1use std::hash::{Hash, Hasher};
2
3fn normalize(value: f32) -> f32 {
4 if value.is_finite() {
5 if value == 0.0 {
6 0.0
7 } else {
8 value
9 }
10 } else {
11 0.0
12 }
13}
14
15fn hash_f32(value: f32, state: &mut impl Hasher) {
16 normalize(value).to_bits().hash(state);
17}
18
19pub(crate) fn normalized_f32_bits(value: f32) -> u32 {
20 normalize(value).to_bits()
21}
22
23#[derive(Clone, Copy, Debug, Default)]
24pub struct Point {
25 pub x: f32,
26 pub y: f32,
27}
28
29impl Point {
30 pub const fn new(x: f32, y: f32) -> Self {
31 Self { x, y }
32 }
33
34 pub fn normalized(self) -> Self {
35 Self::new(normalize(self.x), normalize(self.y))
36 }
37}
38
39impl PartialEq for Point {
40 fn eq(&self, other: &Self) -> bool {
41 self.normalized().x == other.normalized().x && self.normalized().y == other.normalized().y
42 }
43}
44
45impl Eq for Point {}
46
47impl Hash for Point {
48 fn hash<H: Hasher>(&self, state: &mut H) {
49 hash_f32(self.x, state);
50 hash_f32(self.y, state);
51 }
52}
53
54#[derive(Clone, Copy, Debug, Default)]
55pub struct Size {
56 pub width: f32,
57 pub height: f32,
58}
59
60impl Size {
61 pub const fn new(width: f32, height: f32) -> Self {
62 Self { width, height }
63 }
64
65 pub fn normalized(self) -> Self {
66 Self::new(
67 normalize(self.width).max(0.0),
68 normalize(self.height).max(0.0),
69 )
70 }
71}
72
73impl PartialEq for Size {
74 fn eq(&self, other: &Self) -> bool {
75 self.normalized().width == other.normalized().width
76 && self.normalized().height == other.normalized().height
77 }
78}
79
80impl Eq for Size {}
81
82impl Hash for Size {
83 fn hash<H: Hasher>(&self, state: &mut H) {
84 hash_f32(self.normalized().width, state);
85 hash_f32(self.normalized().height, state);
86 }
87}
88
89#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
90pub struct PhysicalPoint {
91 pub x: i32,
92 pub y: i32,
93}
94
95impl PhysicalPoint {
96 pub const fn new(x: i32, y: i32) -> Self {
97 Self { x, y }
98 }
99}
100
101#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
102pub struct PhysicalSize {
103 pub width: i32,
104 pub height: i32,
105}
106
107impl PhysicalSize {
108 pub const fn new(width: i32, height: i32) -> Self {
109 Self { width, height }
110 }
111}
112
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
114pub struct PhysicalRect {
115 pub left: i32,
116 pub top: i32,
117 pub right: i32,
118 pub bottom: i32,
119}
120
121impl PhysicalRect {
122 pub const fn new(left: i32, top: i32, right: i32, bottom: i32) -> Self {
123 Self {
124 left,
125 top,
126 right,
127 bottom,
128 }
129 }
130
131 pub fn width(self) -> i32 {
132 self.right - self.left
133 }
134
135 pub fn height(self) -> i32 {
136 self.bottom - self.top
137 }
138
139 pub fn union(self, other: Self) -> Self {
140 Self::new(
141 self.left.min(other.left),
142 self.top.min(other.top),
143 self.right.max(other.right),
144 self.bottom.max(other.bottom),
145 )
146 }
147
148 pub fn intersect(self, other: Self) -> Option<Self> {
149 let left = self.left.max(other.left);
150 let top = self.top.max(other.top);
151 let right = self.right.min(other.right);
152 let bottom = self.bottom.min(other.bottom);
153 (left < right && top < bottom).then(|| Self::new(left, top, right, bottom))
154 }
155
156 pub fn as_ui_rect(self) -> UiRect {
157 UiRect::new(
158 self.left as f32,
159 self.top as f32,
160 self.right as f32,
161 self.bottom as f32,
162 )
163 }
164}
165
166#[derive(Clone, Copy, Debug, PartialEq)]
167pub struct UiScale(f32);
168
169impl UiScale {
170 pub const ONE: Self = Self(1.0);
171
172 pub fn new(value: f32) -> Self {
173 Self(if value.is_finite() && value > 0.0 {
174 value
175 } else {
176 1.0
177 })
178 }
179
180 pub fn factor(self) -> f32 {
181 self.0
182 }
183
184 pub fn is_identity(self) -> bool {
185 (self.0 - 1.0).abs() < f32::EPSILON
186 }
187
188 pub fn physical_value(self, logical: f32) -> i32 {
189 (normalize(logical) * self.0).round() as i32
190 }
191
192 pub fn physical_ui_value(self, logical: f32) -> f32 {
193 normalize(logical) * self.0
194 }
195
196 pub fn physical_ui_length(self, logical: f32) -> f32 {
197 self.physical_ui_value(logical).max(0.0)
198 }
199
200 pub fn physical_ui_signed_length(self, logical: f32) -> f32 {
201 self.physical_ui_value(logical)
202 }
203
204 pub fn physical_length(self, logical: f32) -> i32 {
205 if logical <= 0.0 {
206 return 0;
207 }
208 self.physical_value(logical).max(1)
209 }
210
211 pub fn physical_signed_length(self, logical: f32) -> i32 {
212 if logical < 0.0 {
213 -self.physical_length(logical.abs())
214 } else if logical > 0.0 {
215 self.physical_length(logical)
216 } else {
217 0
218 }
219 }
220
221 pub fn logical_value(self, physical: i32) -> f32 {
222 physical as f32 / self.0
223 }
224
225 pub fn physical_point(self, logical: Point) -> PhysicalPoint {
226 PhysicalPoint::new(
227 self.physical_value(logical.x),
228 self.physical_value(logical.y),
229 )
230 }
231
232 pub fn physical_ui_point(self, logical: Point) -> Point {
233 Point::new(logical.x * self.0, logical.y * self.0)
234 }
235
236 pub fn logical_point(self, physical: PhysicalPoint) -> Point {
237 Point::new(
238 self.logical_value(physical.x),
239 self.logical_value(physical.y),
240 )
241 }
242
243 pub fn physical_rect(self, logical: UiRect) -> PhysicalRect {
244 let mut left = self.physical_value(logical.left);
245 let mut top = self.physical_value(logical.top);
246 let mut right = self.physical_value(logical.right);
247 let mut bottom = self.physical_value(logical.bottom);
248
249 if logical.right > logical.left && right == left {
250 left = (logical.left * self.0).floor() as i32;
251 right = (logical.right * self.0).ceil() as i32;
252 }
253 if logical.bottom > logical.top && bottom == top {
254 top = (logical.top * self.0).floor() as i32;
255 bottom = (logical.bottom * self.0).ceil() as i32;
256 }
257
258 PhysicalRect::new(left, top, right, bottom)
259 }
260
261 pub fn physical_rect_outward(self, logical: UiRect) -> PhysicalRect {
262 PhysicalRect::new(
263 (logical.left * self.0).floor() as i32,
264 (logical.top * self.0).floor() as i32,
265 (logical.right * self.0).ceil() as i32,
266 (logical.bottom * self.0).ceil() as i32,
267 )
268 }
269
270 pub fn physical_ui_rect(self, logical: UiRect) -> UiRect {
271 UiRect::new(
272 logical.left * self.0,
273 logical.top * self.0,
274 logical.right * self.0,
275 logical.bottom * self.0,
276 )
277 }
278
279 pub fn logical_rect(self, physical: PhysicalRect) -> UiRect {
280 UiRect::new(
281 self.logical_value(physical.left),
282 self.logical_value(physical.top),
283 self.logical_value(physical.right),
284 self.logical_value(physical.bottom),
285 )
286 }
287
288 pub fn logical_size(self, physical: PhysicalSize) -> Size {
289 Size::new(
290 self.logical_value(physical.width).max(1.0),
291 self.logical_value(physical.height).max(1.0),
292 )
293 }
294
295 pub fn physical_size(self, logical: Size) -> PhysicalSize {
296 PhysicalSize::new(
297 self.physical_length(logical.width),
298 self.physical_length(logical.height),
299 )
300 }
301}
302
303#[derive(Clone, Copy, Debug, Default)]
304pub struct EdgeInsets {
305 pub left: f32,
306 pub top: f32,
307 pub right: f32,
308 pub bottom: f32,
309}
310
311impl EdgeInsets {
312 pub const ZERO: Self = Self::all(0.0);
313
314 pub const fn all(value: f32) -> Self {
315 Self {
316 left: value,
317 top: value,
318 right: value,
319 bottom: value,
320 }
321 }
322
323 pub const fn symmetric(horizontal: f32, vertical: f32) -> Self {
324 Self {
325 left: horizontal,
326 top: vertical,
327 right: horizontal,
328 bottom: vertical,
329 }
330 }
331}
332
333impl PartialEq for EdgeInsets {
334 fn eq(&self, other: &Self) -> bool {
335 normalize(self.left) == normalize(other.left)
336 && normalize(self.top) == normalize(other.top)
337 && normalize(self.right) == normalize(other.right)
338 && normalize(self.bottom) == normalize(other.bottom)
339 }
340}
341
342impl Eq for EdgeInsets {}
343
344impl Hash for EdgeInsets {
345 fn hash<H: Hasher>(&self, state: &mut H) {
346 hash_f32(self.left, state);
347 hash_f32(self.top, state);
348 hash_f32(self.right, state);
349 hash_f32(self.bottom, state);
350 }
351}
352
353#[derive(Clone, Copy, Debug, Default)]
354pub struct UiRect {
355 pub left: f32,
356 pub top: f32,
357 pub right: f32,
358 pub bottom: f32,
359}
360
361impl UiRect {
362 pub const fn new(left: f32, top: f32, right: f32, bottom: f32) -> Self {
363 Self {
364 left,
365 top,
366 right,
367 bottom,
368 }
369 }
370
371 pub fn normalized(self) -> Self {
372 let left = normalize(self.left);
373 let top = normalize(self.top);
374 let right = normalize(self.right);
375 let bottom = normalize(self.bottom);
376 Self::new(
377 left.min(right),
378 top.min(bottom),
379 left.max(right),
380 top.max(bottom),
381 )
382 }
383
384 pub fn width(self) -> f32 {
385 self.right - self.left
386 }
387
388 pub fn height(self) -> f32 {
389 self.bottom - self.top
390 }
391
392 pub fn contains(self, point: Point) -> bool {
393 point.x >= self.left && point.x < self.right && point.y >= self.top && point.y < self.bottom
394 }
395
396 pub fn inflate(self, x: f32, y: f32) -> Self {
397 Self::new(self.left - x, self.top - y, self.right + x, self.bottom + y)
398 }
399
400 pub fn translate(self, x: f32, y: f32) -> Self {
401 Self::new(self.left + x, self.top + y, self.right + x, self.bottom + y)
402 }
403
404 pub fn inset(self, insets: EdgeInsets) -> Self {
405 Self::new(
406 self.left + insets.left,
407 self.top + insets.top,
408 self.right - insets.right,
409 self.bottom - insets.bottom,
410 )
411 }
412
413 pub fn union(self, other: Self) -> Self {
414 Self::new(
415 self.left.min(other.left),
416 self.top.min(other.top),
417 self.right.max(other.right),
418 self.bottom.max(other.bottom),
419 )
420 }
421
422 pub fn intersect(self, other: Self) -> Option<Self> {
423 let left = self.left.max(other.left);
424 let top = self.top.max(other.top);
425 let right = self.right.min(other.right);
426 let bottom = self.bottom.min(other.bottom);
427 (left < right && top < bottom).then(|| Self::new(left, top, right, bottom))
428 }
429}
430
431impl PartialEq for UiRect {
432 fn eq(&self, other: &Self) -> bool {
433 self.normalized().left == other.normalized().left
434 && self.normalized().top == other.normalized().top
435 && self.normalized().right == other.normalized().right
436 && self.normalized().bottom == other.normalized().bottom
437 }
438}
439
440impl Eq for UiRect {}
441
442impl Hash for UiRect {
443 fn hash<H: Hasher>(&self, state: &mut H) {
444 let rect = self.normalized();
445 hash_f32(rect.left, state);
446 hash_f32(rect.top, state);
447 hash_f32(rect.right, state);
448 hash_f32(rect.bottom, state);
449 }
450}
451
452#[cfg(test)]
453#[path = "geometry_test.rs"]
454mod tests;