Skip to main content

sequoia_core/
geometry.rs

1use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
2
3#[derive(Clone, Copy, Debug, Default, PartialEq)]
4pub struct Point {
5    pub x: f32,
6
7    pub y: f32,
8}
9
10impl Point {
11    pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
12
13    pub const fn new(x: f32, y: f32) -> Self {
14        Self { x, y }
15    }
16
17    pub fn distance(self, other: Self) -> f32 {
18        (other - self).length()
19    }
20}
21
22impl From<(f32, f32)> for Point {
23    fn from((x, y): (f32, f32)) -> Self {
24        Self { x, y }
25    }
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq)]
29pub struct Vec2 {
30    pub x: f32,
31
32    pub y: f32,
33}
34
35impl Vec2 {
36    pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
37
38    pub const fn new(x: f32, y: f32) -> Self {
39        Self { x, y }
40    }
41
42    pub fn length_squared(self) -> f32 {
43        self.x * self.x + self.y * self.y
44    }
45
46    pub fn length(self) -> f32 {
47        self.length_squared().sqrt()
48    }
49
50    pub fn dot(self, other: Self) -> f32 {
51        self.x * other.x + self.y * other.y
52    }
53
54    pub fn normalized(self) -> Self {
55        let len = self.length();
56        if len > 0.0 {
57            Self::new(self.x / len, self.y / len)
58        } else {
59            Self::ZERO
60        }
61    }
62}
63
64impl From<Point> for Vec2 {
65    fn from(p: Point) -> Self {
66        Self { x: p.x, y: p.y }
67    }
68}
69
70impl From<Vec2> for Point {
71    fn from(v: Vec2) -> Self {
72        Self { x: v.x, y: v.y }
73    }
74}
75
76impl Add for Vec2 {
77    type Output = Self;
78    fn add(self, rhs: Self) -> Self {
79        Self::new(self.x + rhs.x, self.y + rhs.y)
80    }
81}
82
83impl Sub for Vec2 {
84    type Output = Self;
85    fn sub(self, rhs: Self) -> Self {
86        Self::new(self.x - rhs.x, self.y - rhs.y)
87    }
88}
89
90impl Neg for Vec2 {
91    type Output = Self;
92    fn neg(self) -> Self {
93        Self::new(-self.x, -self.y)
94    }
95}
96
97impl Mul<f32> for Vec2 {
98    type Output = Self;
99    fn mul(self, s: f32) -> Self {
100        Self::new(self.x * s, self.y * s)
101    }
102}
103
104impl AddAssign for Vec2 {
105    fn add_assign(&mut self, rhs: Self) {
106        *self = *self + rhs;
107    }
108}
109
110impl SubAssign for Vec2 {
111    fn sub_assign(&mut self, rhs: Self) {
112        *self = *self - rhs;
113    }
114}
115
116impl Add<Vec2> for Point {
117    type Output = Self;
118    fn add(self, rhs: Vec2) -> Self {
119        Self::new(self.x + rhs.x, self.y + rhs.y)
120    }
121}
122
123impl Sub<Vec2> for Point {
124    type Output = Self;
125    fn sub(self, rhs: Vec2) -> Self {
126        Self::new(self.x - rhs.x, self.y - rhs.y)
127    }
128}
129
130impl Sub for Point {
131    type Output = Vec2;
132    fn sub(self, rhs: Self) -> Vec2 {
133        Vec2::new(self.x - rhs.x, self.y - rhs.y)
134    }
135}
136
137#[derive(Clone, Copy, Debug, Default, PartialEq)]
138pub struct Size {
139    pub width: f32,
140
141    pub height: f32,
142}
143
144impl Size {
145    pub const ZERO: Self = Self {
146        width: 0.0,
147        height: 0.0,
148    };
149
150    pub const fn new(width: f32, height: f32) -> Self {
151        Self { width, height }
152    }
153
154    pub fn is_empty(self) -> bool {
155        self.width <= 0.0 || self.height <= 0.0
156    }
157}
158
159impl From<(f32, f32)> for Size {
160    fn from((w, h): (f32, f32)) -> Self {
161        Self {
162            width: w,
163            height: h,
164        }
165    }
166}
167
168#[derive(Clone, Copy, Debug, Default, PartialEq)]
169pub struct Rect {
170    pub origin: Point,
171
172    pub size: Size,
173}
174
175impl Rect {
176    pub const ZERO: Self = Self {
177        origin: Point::ZERO,
178        size: Size::ZERO,
179    };
180
181    pub const fn new(origin: Point, size: Size) -> Self {
182        Self { origin, size }
183    }
184
185    pub const fn from_xywh(x: f32, y: f32, width: f32, height: f32) -> Self {
186        Self::new(Point::new(x, y), Size::new(width, height))
187    }
188
189    pub fn left(self) -> f32 {
190        self.origin.x
191    }
192
193    pub fn right(self) -> f32 {
194        self.origin.x + self.size.width
195    }
196
197    pub fn top(self) -> f32 {
198        self.origin.y
199    }
200
201    pub fn bottom(self) -> f32 {
202        self.origin.y + self.size.height
203    }
204
205    pub fn is_empty(self) -> bool {
206        self.size.is_empty()
207    }
208
209    pub fn contains(self, p: Point) -> bool {
210        p.x >= self.left() && p.x < self.right() && p.y >= self.top() && p.y < self.bottom()
211    }
212
213    pub fn contains_rect(self, other: Rect) -> bool {
214        !other.is_empty()
215            && other.left() >= self.left()
216            && other.top() >= self.top()
217            && other.right() <= self.right()
218            && other.bottom() <= self.bottom()
219    }
220
221    pub fn intersects(self, other: Rect) -> bool {
222        !self.intersect(other).is_empty()
223    }
224
225    pub fn intersect(self, other: Rect) -> Rect {
226        let x0 = self.left().max(other.left());
227        let y0 = self.top().max(other.top());
228        let x1 = self.right().min(other.right());
229        let y1 = self.bottom().min(other.bottom());
230        Rect::from_xywh(x0, y0, (x1 - x0).max(0.0), (y1 - y0).max(0.0))
231    }
232
233    pub fn union(self, other: Rect) -> Rect {
234        if self.is_empty() {
235            return other;
236        }
237        if other.is_empty() {
238            return self;
239        }
240        let x0 = self.left().min(other.left());
241        let y0 = self.top().min(other.top());
242        let x1 = self.right().max(other.right());
243        let y1 = self.bottom().max(other.bottom());
244        Rect::from_xywh(x0, y0, x1 - x0, y1 - y0)
245    }
246
247    pub fn inflate(self, dx: f32, dy: f32) -> Rect {
248        Rect::from_xywh(
249            self.left() - dx,
250            self.top() - dy,
251            self.size.width + dx * 2.0,
252            self.size.height + dy * 2.0,
253        )
254    }
255
256    pub fn translate(self, v: Vec2) -> Rect {
257        Rect::new(self.origin + v, self.size)
258    }
259
260    pub fn center(self) -> Point {
261        Point::new(
262            self.origin.x + self.size.width * 0.5,
263            self.origin.y + self.size.height * 0.5,
264        )
265    }
266}
267
268#[derive(Clone, Copy, Debug, PartialEq)]
269pub struct Transform {
270    pub m: [f32; 6],
271}
272
273impl Default for Transform {
274    fn default() -> Self {
275        Self::IDENTITY
276    }
277}
278
279impl Transform {
280    pub const IDENTITY: Self = Self {
281        m: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
282    };
283
284    pub const fn translate(x: f32, y: f32) -> Self {
285        Self {
286            m: [1.0, 0.0, 0.0, 1.0, x, y],
287        }
288    }
289
290    pub const fn scale(sx: f32, sy: f32) -> Self {
291        Self {
292            m: [sx, 0.0, 0.0, sy, 0.0, 0.0],
293        }
294    }
295
296    pub const fn uniform(s: f32) -> Self {
297        Self::scale(s, s)
298    }
299
300    pub fn rotate(radians: f32) -> Self {
301        let (s, c) = radians.sin_cos();
302        Self {
303            m: [c, s, -s, c, 0.0, 0.0],
304        }
305    }
306
307    pub fn rotate_degrees(degrees: f32) -> Self {
308        Self::rotate(degrees.to_radians())
309    }
310
311    pub fn rotate_around(radians: f32, pivot: Point) -> Self {
312        Self::translate(-pivot.x, -pivot.y)
313            .then(Self::rotate(radians))
314            .then(Self::translate(pivot.x, pivot.y))
315    }
316
317    pub fn rotate_around_degrees(degrees: f32, pivot: Point) -> Self {
318        Self::rotate_around(degrees.to_radians(), pivot)
319    }
320
321    pub fn then(self, next: Self) -> Self {
322        let [a1, b1, c1, d1, e1, f1] = self.m;
323        let [a2, b2, c2, d2, e2, f2] = next.m;
324        Self {
325            m: [
326                a1 * a2 + b1 * c2,
327                a1 * b2 + b1 * d2,
328                c1 * a2 + d1 * c2,
329                c1 * b2 + d1 * d2,
330                e1 * a2 + f1 * c2 + e2,
331                e1 * b2 + f1 * d2 + f2,
332            ],
333        }
334    }
335
336    pub fn apply(self, p: Point) -> Point {
337        let [a, b, c, d, e, f] = self.m;
338        Point::new(a * p.x + c * p.y + e, b * p.x + d * p.y + f)
339    }
340
341    pub fn apply_vec(self, v: Vec2) -> Vec2 {
342        let [a, b, c, d, ..] = self.m;
343        Vec2::new(a * v.x + c * v.y, b * v.x + d * v.y)
344    }
345
346    pub fn apply_rect_aabb(self, r: Rect) -> Rect {
347        if self.is_identity() {
348            return r;
349        }
350        let p0 = self.apply(r.origin);
351        let p1 = self.apply(Point::new(r.right(), r.top()));
352        let p2 = self.apply(Point::new(r.right(), r.bottom()));
353        let p3 = self.apply(Point::new(r.left(), r.bottom()));
354        let x0 = p0.x.min(p1.x).min(p2.x).min(p3.x);
355        let y0 = p0.y.min(p1.y).min(p2.y).min(p3.y);
356        let x1 = p0.x.max(p1.x).max(p2.x).max(p3.x);
357        let y1 = p0.y.max(p1.y).max(p2.y).max(p3.y);
358        Rect::from_xywh(x0, y0, (x1 - x0).max(0.0), (y1 - y0).max(0.0))
359    }
360
361    pub fn is_identity(self) -> bool {
362        let [a, b, c, d, e, f] = self.m;
363        a == 1.0 && b == 0.0 && c == 0.0 && d == 1.0 && e == 0.0 && f == 0.0
364    }
365
366    pub fn determinant(self) -> f32 {
367        let [a, b, c, d, ..] = self.m;
368        a * d - b * c
369    }
370
371    pub fn max_scale(self) -> f32 {
372        let [a, b, c, d, ..] = self.m;
373        let sx = (a * a + b * b).sqrt();
374        let sy = (c * c + d * d).sqrt();
375        sx.max(sy).max(f32::MIN_POSITIVE)
376    }
377
378    pub fn similarity_scale(self) -> Option<f32> {
379        let [a, b, c, d, ..] = self.m;
380        let col1 = a * a + b * b;
381        let col2 = c * c + d * d;
382        let dot = a * c + b * d;
383        let scale_hint = (col1 + col2).sqrt();
384        if col1 <= 0.0 || col2 <= 0.0 {
385            return None;
386        }
387        if dot.abs() > 1e-4 * scale_hint || (col1 - col2).abs() > 1e-4 * scale_hint {
388            return None;
389        }
390        Some(col1.sqrt())
391    }
392
393    pub fn inverse(self) -> Option<Self> {
394        let det = self.determinant();
395        if det.abs() < f32::EPSILON {
396            return None;
397        }
398        let [a, b, c, d, e, f] = self.m;
399        let inv = 1.0 / det;
400        Some(Self {
401            m: [
402                d * inv,
403                -b * inv,
404                -c * inv,
405                a * inv,
406                (c * f - d * e) * inv,
407                (b * e - a * f) * inv,
408            ],
409        })
410    }
411}
412
413#[derive(Clone, Copy, Debug, Default, PartialEq)]
414#[non_exhaustive]
415pub struct Edges {
416    pub left: f32,
417
418    pub top: f32,
419
420    pub right: f32,
421
422    pub bottom: f32,
423}
424
425impl Edges {
426    pub const ZERO: Self = Self {
427        left: 0.0,
428        top: 0.0,
429        right: 0.0,
430        bottom: 0.0,
431    };
432
433    pub const fn uniform(v: f32) -> Self {
434        Self {
435            left: v,
436            top: v,
437            right: v,
438            bottom: v,
439        }
440    }
441
442    pub const fn horizontal(v: f32) -> Self {
443        Self {
444            left: v,
445            top: 0.0,
446            right: v,
447            bottom: 0.0,
448        }
449    }
450
451    pub const fn vertical(v: f32) -> Self {
452        Self {
453            left: 0.0,
454            top: v,
455            right: 0.0,
456            bottom: v,
457        }
458    }
459
460    pub const fn all(v: f32) -> Self {
461        Self::uniform(v)
462    }
463
464    pub const fn symmetric(horizontal: f32, vertical: f32) -> Self {
465        Self {
466            left: horizontal,
467            top: vertical,
468            right: horizontal,
469            bottom: vertical,
470        }
471    }
472
473    pub const fn xy(x: f32, y: f32) -> Self {
474        Self::symmetric(x, y)
475    }
476
477    pub const fn only(left: f32, top: f32, right: f32, bottom: f32) -> Self {
478        Self {
479            left,
480            top,
481            right,
482            bottom,
483        }
484    }
485
486    pub const fn left(v: f32) -> Self {
487        Self {
488            left: v,
489            top: 0.0,
490            right: 0.0,
491            bottom: 0.0,
492        }
493    }
494
495    pub const fn top(v: f32) -> Self {
496        Self {
497            left: 0.0,
498            top: v,
499            right: 0.0,
500            bottom: 0.0,
501        }
502    }
503
504    pub const fn right(v: f32) -> Self {
505        Self {
506            left: 0.0,
507            top: 0.0,
508            right: v,
509            bottom: 0.0,
510        }
511    }
512
513    pub const fn bottom(v: f32) -> Self {
514        Self {
515            left: 0.0,
516            top: 0.0,
517            right: 0.0,
518            bottom: v,
519        }
520    }
521
522    pub fn with_left(mut self, v: f32) -> Self {
523        self.left = v;
524        self
525    }
526
527    pub fn with_top(mut self, v: f32) -> Self {
528        self.top = v;
529        self
530    }
531
532    pub fn with_right(mut self, v: f32) -> Self {
533        self.right = v;
534        self
535    }
536
537    pub fn with_bottom(mut self, v: f32) -> Self {
538        self.bottom = v;
539        self
540    }
541
542    pub fn with_horizontal(mut self, v: f32) -> Self {
543        self.left = v;
544        self.right = v;
545        self
546    }
547
548    pub fn with_vertical(mut self, v: f32) -> Self {
549        self.top = v;
550        self.bottom = v;
551        self
552    }
553
554    pub fn is_zero(self) -> bool {
555        self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
556    }
557
558    pub fn horizontal_sum(self) -> f32 {
559        self.left + self.right
560    }
561
562    pub fn vertical_sum(self) -> f32 {
563        self.top + self.bottom
564    }
565}
566
567#[derive(Clone, Copy, Debug, PartialEq)]
568pub enum Length {
569    Px(f32),
570
571    Percent(f32),
572
573    Fill(f32),
574
575    Fit,
576}
577
578impl Length {
579    pub const FIT: Self = Self::Fit;
580
581    pub fn px(v: f32) -> Self {
582        Self::Px(v.max(0.0))
583    }
584
585    pub fn percent(p: f32) -> Self {
586        Self::Percent(p)
587    }
588
589    pub fn fill(weight: f32) -> Self {
590        Self::Fill(weight)
591    }
592
593    pub fn fit() -> Self {
594        Self::Fit
595    }
596
597    pub fn resolve(self, available: f32) -> Option<f32> {
598        match self {
599            Self::Px(v) => Some(v),
600            Self::Percent(p) => Some(available * p / 100.0),
601            Self::Fill(_) | Self::Fit => None,
602        }
603    }
604
605    pub fn is_fill(self) -> bool {
606        matches!(self, Self::Fill(_))
607    }
608
609    pub fn is_fit(self) -> bool {
610        matches!(self, Self::Fit)
611    }
612}
613
614impl From<f32> for Length {
615    fn from(v: f32) -> Self {
616        Self::px(v)
617    }
618}
619
620pub trait LengthExt {
621    fn px(self) -> Length;
622
623    fn percent(self) -> Length;
624
625    fn fill(self) -> Length;
626}
627
628macro_rules! impl_length_ext {
629    ($($t:ty),* $(,)?) => {
630        $(
631            impl LengthExt for $t {
632                fn px(self) -> Length { Length::px(self as f32) }
633                fn percent(self) -> Length { Length::percent(self as f32) }
634                fn fill(self) -> Length { Length::fill(self as f32) }
635            }
636        )*
637    };
638}
639
640impl_length_ext!(f32, i32, u16, u32);
641
642impl From<f32> for Edges {
643    fn from(v: f32) -> Self {
644        Self::uniform(v)
645    }
646}
647
648macro_rules! impl_edges_from_int {
649    ($($t:ty),* $(,)?) => {
650        $(
651            impl From<$t> for Edges {
652                fn from(v: $t) -> Self {
653                    Self::uniform(v as f32)
654                }
655            }
656        )*
657    };
658}
659
660impl_edges_from_int!(i32, u32, u16, i16, u8, i8);
661
662impl From<(f32, f32)> for Edges {
663    fn from((x, y): (f32, f32)) -> Self {
664        Self::xy(x, y)
665    }
666}
667
668impl From<[f32; 2]> for Edges {
669    fn from([x, y]: [f32; 2]) -> Self {
670        Self::xy(x, y)
671    }
672}
673
674impl From<[f32; 4]> for Edges {
675    fn from([left, top, right, bottom]: [f32; 4]) -> Self {
676        Self::only(left, top, right, bottom)
677    }
678}
679
680impl From<(f32, f32, f32, f32)> for Edges {
681    fn from((left, top, right, bottom): (f32, f32, f32, f32)) -> Self {
682        Self::only(left, top, right, bottom)
683    }
684}
685
686impl Add for Edges {
687    type Output = Self;
688
689    fn add(self, rhs: Self) -> Self {
690        Self {
691            left: self.left + rhs.left,
692            top: self.top + rhs.top,
693            right: self.right + rhs.right,
694            bottom: self.bottom + rhs.bottom,
695        }
696    }
697}
698
699#[derive(Clone, Copy, Debug, PartialEq)]
700pub struct Constraints {
701    pub min: Size,
702
703    pub max: Size,
704}
705
706impl Constraints {
707    pub const UNBOUNDED: Self = Self {
708        min: Size::ZERO,
709        max: Size {
710            width: f32::INFINITY,
711            height: f32::INFINITY,
712        },
713    };
714
715    pub const fn tight(size: Size) -> Self {
716        Self {
717            min: size,
718            max: size,
719        }
720    }
721
722    pub const fn loose(max: Size) -> Self {
723        Self {
724            min: Size::ZERO,
725            max,
726        }
727    }
728
729    pub fn loosen(self) -> Self {
730        Self {
731            min: Size::ZERO,
732            max: self.max,
733        }
734    }
735
736    pub fn constrain(self, size: Size) -> Size {
737        Size::new(
738            clamp_axis(size.width, self.min.width, self.max.width),
739            clamp_axis(size.height, self.min.height, self.max.height),
740        )
741    }
742
743    pub fn is_tight(self) -> bool {
744        self.min == self.max
745    }
746
747    pub fn is_bounded(self) -> bool {
748        self.max.width.is_finite() && self.max.height.is_finite()
749    }
750
751    pub fn with_width(self, min: f32, max: f32) -> Self {
752        Self {
753            min: Size::new(min, self.min.height),
754            max: Size::new(max, self.max.height),
755        }
756    }
757
758    pub fn with_height(self, min: f32, max: f32) -> Self {
759        Self {
760            min: Size::new(self.min.width, min),
761            max: Size::new(self.max.width, max),
762        }
763    }
764}
765
766fn clamp_axis(value: f32, min: f32, max: f32) -> f32 {
767    let (min, max) = if min > max { (max, max) } else { (min, max) };
768    if value < min {
769        min
770    } else if value > max {
771        max
772    } else {
773        value
774    }
775}
776
777#[cfg(test)]
778mod tests {
779    use super::*;
780
781    fn rect(x: f32, y: f32, w: f32, h: f32) -> Rect {
782        Rect::from_xywh(x, y, w, h)
783    }
784
785    #[test]
786    fn point_arithmetic() {
787        let a = Point::new(2.0, 3.0);
788        let b = Point::new(5.0, 7.0);
789        assert_eq!(b - a, Vec2::new(3.0, 4.0));
790        assert_eq!(a + Vec2::new(1.0, 1.0), Point::new(3.0, 4.0));
791        assert_eq!(a.distance(Point::ZERO), (13.0f32).sqrt());
792    }
793
794    #[test]
795    fn vec_math() {
796        let v = Vec2::new(3.0, 4.0);
797        assert_eq!(v.length(), 5.0);
798        assert_eq!(v.dot(Vec2::new(1.0, 0.0)), 3.0);
799        assert_eq!(Vec2::ZERO.normalized(), Vec2::ZERO);
800        assert_eq!(-v, Vec2::new(-3.0, -4.0));
801    }
802
803    #[test]
804    fn rect_ltrb_and_contains() {
805        let r = rect(10.0, 20.0, 30.0, 40.0);
806        assert_eq!(r.left(), 10.0);
807        assert_eq!(r.right(), 40.0);
808        assert_eq!(r.top(), 20.0);
809        assert_eq!(r.bottom(), 60.0);
810
811        assert!(r.contains(Point::new(39.9, 59.9)));
812        assert!(!r.contains(Point::new(40.0, 59.9)));
813        assert!(!r.contains(Point::new(39.9, 60.0)));
814        assert!(!r.contains(Point::new(9.9, 20.0)));
815    }
816
817    #[test]
818    fn rect_intersect() {
819        let a = rect(0.0, 0.0, 10.0, 10.0);
820        let touching = rect(10.0, 0.0, 10.0, 10.0);
821        assert!(!a.intersects(touching));
822
823        let overlap = rect(5.0, 5.0, 10.0, 10.0);
824        assert_eq!(a.intersect(overlap), rect(5.0, 5.0, 5.0, 5.0));
825
826        let disjoint = rect(100.0, 100.0, 1.0, 1.0);
827        assert!(a.intersect(disjoint).is_empty());
828        assert!(!a.intersects(disjoint));
829    }
830
831    #[test]
832    fn rect_union() {
833        let a = rect(0.0, 0.0, 10.0, 10.0);
834        let b = rect(20.0, 20.0, 10.0, 10.0);
835        assert_eq!(a.union(b), rect(0.0, 0.0, 30.0, 30.0));
836
837        let empty = rect(5.0, 5.0, 0.0, 0.0);
838        assert_eq!(empty.union(a), a);
839        assert_eq!(a.union(empty), a);
840    }
841
842    #[test]
843    fn rect_contains_rect_and_inflate() {
844        let outer = rect(0.0, 0.0, 10.0, 10.0);
845        assert!(outer.contains_rect(rect(2.0, 2.0, 3.0, 3.0)));
846        assert!(!outer.contains_rect(rect(9.0, 9.0, 5.0, 5.0)));
847
848        let inflated = outer.inflate(1.0, 2.0);
849        assert_eq!(inflated.origin, Point::new(-1.0, -2.0));
850        assert_eq!(inflated.size, Size::new(12.0, 14.0));
851
852        assert_eq!(
853            outer.translate(Vec2::new(5.0, -5.0)),
854            rect(5.0, -5.0, 10.0, 10.0)
855        );
856    }
857
858    #[test]
859    fn edges_sums() {
860        let e = Edges::uniform(2.0);
861        assert_eq!(e.horizontal_sum(), 4.0);
862        assert_eq!(e.vertical_sum(), 4.0);
863        assert_eq!(Edges::horizontal(3.0).vertical_sum(), 0.0);
864    }
865
866    #[test]
867    fn edges_ergonomics() {
868        assert_eq!(Edges::all(4.0), Edges::uniform(4.0));
869        assert_eq!(Edges::xy(10.0, 6.0), Edges::symmetric(10.0, 6.0));
870        assert_eq!(Edges::xy(10.0, 6.0), Edges::only(10.0, 6.0, 10.0, 6.0));
871        assert_eq!(Edges::from((10.0f32, 6.0f32)), Edges::xy(10.0, 6.0));
872        assert_eq!(Edges::from([10.0f32, 6.0f32]), Edges::xy(10.0, 6.0));
873        assert_eq!(
874            Edges::from([1.0f32, 2.0, 3.0, 4.0]),
875            Edges::only(1.0, 2.0, 3.0, 4.0)
876        );
877        assert_eq!(
878            Edges::from((1.0f32, 2.0, 3.0, 4.0)),
879            Edges::only(1.0, 2.0, 3.0, 4.0)
880        );
881        assert_eq!(Edges::from(8), Edges::all(8.0));
882        assert_eq!(
883            Edges::left(5.0).with_top(3.0),
884            Edges::only(5.0, 3.0, 0.0, 0.0)
885        );
886        assert!(Edges::ZERO.is_zero());
887        assert!(!Edges::all(1.0).is_zero());
888        assert_eq!(
889            Edges::all(2.0) + Edges::xy(1.0, 3.0),
890            Edges::only(3.0, 5.0, 3.0, 5.0)
891        );
892    }
893
894    #[test]
895    fn length_resolve() {
896        assert_eq!(Length::from(12.5), Length::Px(12.5));
897        assert_eq!(Length::Px(100.0).resolve(1000.0), Some(100.0));
898        assert_eq!(Length::Percent(50.0).resolve(200.0), Some(100.0));
899        assert_eq!(Length::Fill(1.0).resolve(1000.0), None);
900        assert_eq!(Length::FIT.resolve(1000.0), None);
901        assert_eq!(Length::px(-5.0), Length::Px(0.0));
902    }
903
904    #[test]
905    fn length_ext_sugar() {
906        assert_eq!(100.px(), Length::Px(100.0));
907        assert_eq!(50.percent(), Length::Percent(50.0));
908        assert_eq!(1.0.fill(), Length::Fill(1.0));
909    }
910
911    #[test]
912    fn constraints_constrain() {
913        assert_eq!(
914            Constraints::UNBOUNDED.constrain(Size::new(1e9, 1e9)),
915            Size::new(1e9, 1e9)
916        );
917        assert_eq!(
918            Constraints::tight(Size::new(50.0, 50.0)).constrain(Size::new(1.0, 999.0)),
919            Size::new(50.0, 50.0)
920        );
921        assert_eq!(
922            Constraints::loose(Size::new(100.0, 100.0)).constrain(Size::new(150.0, 50.0)),
923            Size::new(100.0, 50.0)
924        );
925
926        let c = Constraints {
927            min: Size::new(50.0, 50.0),
928            max: Size::new(100.0, 100.0),
929        };
930        assert_eq!(c.constrain(Size::new(10.0, 500.0)), Size::new(50.0, 100.0));
931
932        let inverted = Constraints {
933            min: Size::new(100.0, 100.0),
934            max: Size::new(50.0, 50.0),
935        };
936        assert_eq!(
937            inverted.constrain(Size::new(75.0, 75.0)),
938            Size::new(50.0, 50.0)
939        );
940    }
941
942    #[test]
943    fn constraints_builders() {
944        let c = Constraints::UNBOUNDED.with_width(10.0, 20.0);
945        assert_eq!(c.constrain(Size::new(100.0, 100.0)), Size::new(20.0, 100.0));
946        assert!(!c.is_tight());
947        assert!(!c.is_bounded());
948        assert!(c.loosen().min == Size::ZERO);
949        assert!(Constraints::tight(Size::new(4.0, 4.0)).is_tight());
950    }
951
952    #[test]
953    fn transform_identity_and_compose() {
954        let p = Point::new(3.0, -2.0);
955        assert_eq!(Transform::IDENTITY.apply(p), p);
956        assert!(Transform::IDENTITY.is_identity());
957        let t = Transform::translate(5.0, -1.0).then(Transform::translate(-5.0, 1.0));
958        assert!(t.is_identity(), "offsets cancel: {t:?}");
959        let r = Transform::rotate_around_degrees(90.0, Point::new(5.0, 5.0));
960        let q = r.apply(Point::new(10.0, 5.0));
961        assert!((q.x - 5.0).abs() < 1e-4, "x={}", q.x);
962        assert!((q.y - 10.0).abs() < 1e-4, "y={}", q.y);
963    }
964
965    #[test]
966    fn transform_aabb_of_rotated_rect() {
967        let r = Rect::from_xywh(0.0, 0.0, 10.0, 10.0);
968        let t = Transform::rotate_around_degrees(45.0, r.center());
969        let aabb = t.apply_rect_aabb(r);
970        let expected = 10.0 * std::f32::consts::SQRT_2;
971        assert!((aabb.size.width - expected).abs() < 0.05, "{aabb:?}");
972        assert!((aabb.size.height - expected).abs() < 0.05, "{aabb:?}");
973    }
974}