Skip to main content

lgui_core/core/foundation/
geometry.rs

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;