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    /// Builds asymmetric edges in CSS order: top, right, bottom, left.
487    ///
488    /// This is the counterpart of [`Self::only`], which takes the fields in
489    /// left, top, right, bottom order.
490    pub const fn trbl(top: f32, right: f32, bottom: f32, left: f32) -> Self {
491        Self {
492            left,
493            top,
494            right,
495            bottom,
496        }
497    }
498
499    pub const fn left(v: f32) -> Self {
500        Self {
501            left: v,
502            top: 0.0,
503            right: 0.0,
504            bottom: 0.0,
505        }
506    }
507
508    pub const fn top(v: f32) -> Self {
509        Self {
510            left: 0.0,
511            top: v,
512            right: 0.0,
513            bottom: 0.0,
514        }
515    }
516
517    pub const fn right(v: f32) -> Self {
518        Self {
519            left: 0.0,
520            top: 0.0,
521            right: v,
522            bottom: 0.0,
523        }
524    }
525
526    pub const fn bottom(v: f32) -> Self {
527        Self {
528            left: 0.0,
529            top: 0.0,
530            right: 0.0,
531            bottom: v,
532        }
533    }
534
535    pub fn with_left(mut self, v: f32) -> Self {
536        self.left = v;
537        self
538    }
539
540    pub fn with_top(mut self, v: f32) -> Self {
541        self.top = v;
542        self
543    }
544
545    pub fn with_right(mut self, v: f32) -> Self {
546        self.right = v;
547        self
548    }
549
550    pub fn with_bottom(mut self, v: f32) -> Self {
551        self.bottom = v;
552        self
553    }
554
555    pub fn with_horizontal(mut self, v: f32) -> Self {
556        self.left = v;
557        self.right = v;
558        self
559    }
560
561    pub fn with_vertical(mut self, v: f32) -> Self {
562        self.top = v;
563        self.bottom = v;
564        self
565    }
566
567    pub fn is_zero(self) -> bool {
568        self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
569    }
570
571    pub fn horizontal_sum(self) -> f32 {
572        self.left + self.right
573    }
574
575    pub fn vertical_sum(self) -> f32 {
576        self.top + self.bottom
577    }
578}
579
580#[derive(Clone, Copy, Debug, PartialEq)]
581pub enum Length {
582    Px(f32),
583
584    Percent(f32),
585
586    Fill(f32),
587
588    Fit,
589}
590
591impl Length {
592    pub const FIT: Self = Self::Fit;
593
594    pub fn px(v: f32) -> Self {
595        Self::Px(v.max(0.0))
596    }
597
598    pub fn percent(p: f32) -> Self {
599        Self::Percent(p)
600    }
601
602    pub fn fill(weight: f32) -> Self {
603        Self::Fill(weight)
604    }
605
606    pub fn fit() -> Self {
607        Self::Fit
608    }
609
610    pub fn resolve(self, available: f32) -> Option<f32> {
611        match self {
612            Self::Px(v) => Some(v),
613            Self::Percent(p) => Some(available * p / 100.0),
614            Self::Fill(_) | Self::Fit => None,
615        }
616    }
617
618    pub fn is_fill(self) -> bool {
619        matches!(self, Self::Fill(_))
620    }
621
622    pub fn is_fit(self) -> bool {
623        matches!(self, Self::Fit)
624    }
625}
626
627impl From<f32> for Length {
628    fn from(v: f32) -> Self {
629        Self::px(v)
630    }
631}
632
633pub trait LengthExt {
634    fn px(self) -> Length;
635
636    fn percent(self) -> Length;
637
638    fn fill(self) -> Length;
639}
640
641macro_rules! impl_length_ext {
642    ($($t:ty),* $(,)?) => {
643        $(
644            impl LengthExt for $t {
645                fn px(self) -> Length { Length::px(self as f32) }
646                fn percent(self) -> Length { Length::percent(self as f32) }
647                fn fill(self) -> Length { Length::fill(self as f32) }
648            }
649        )*
650    };
651}
652
653impl_length_ext!(f32, i32, u16, u32);
654
655impl From<f32> for Edges {
656    fn from(v: f32) -> Self {
657        Self::uniform(v)
658    }
659}
660
661macro_rules! impl_edges_from_int {
662    ($($t:ty),* $(,)?) => {
663        $(
664            impl From<$t> for Edges {
665                fn from(v: $t) -> Self {
666                    Self::uniform(v as f32)
667                }
668            }
669        )*
670    };
671}
672
673impl_edges_from_int!(i32, u32, u16, i16, u8, i8);
674
675impl From<(f32, f32)> for Edges {
676    fn from((x, y): (f32, f32)) -> Self {
677        Self::xy(x, y)
678    }
679}
680
681impl From<[f32; 2]> for Edges {
682    fn from([x, y]: [f32; 2]) -> Self {
683        Self::xy(x, y)
684    }
685}
686
687impl From<[f32; 4]> for Edges {
688    fn from([left, top, right, bottom]: [f32; 4]) -> Self {
689        Self::only(left, top, right, bottom)
690    }
691}
692
693impl From<(f32, f32, f32, f32)> for Edges {
694    fn from((left, top, right, bottom): (f32, f32, f32, f32)) -> Self {
695        Self::only(left, top, right, bottom)
696    }
697}
698
699impl Add for Edges {
700    type Output = Self;
701
702    fn add(self, rhs: Self) -> Self {
703        Self {
704            left: self.left + rhs.left,
705            top: self.top + rhs.top,
706            right: self.right + rhs.right,
707            bottom: self.bottom + rhs.bottom,
708        }
709    }
710}
711
712#[derive(Clone, Copy, Debug, PartialEq)]
713pub struct Constraints {
714    pub min: Size,
715
716    pub max: Size,
717}
718
719impl Constraints {
720    pub const UNBOUNDED: Self = Self {
721        min: Size::ZERO,
722        max: Size {
723            width: f32::INFINITY,
724            height: f32::INFINITY,
725        },
726    };
727
728    pub const fn tight(size: Size) -> Self {
729        Self {
730            min: size,
731            max: size,
732        }
733    }
734
735    pub const fn loose(max: Size) -> Self {
736        Self {
737            min: Size::ZERO,
738            max,
739        }
740    }
741
742    pub fn loosen(self) -> Self {
743        Self {
744            min: Size::ZERO,
745            max: self.max,
746        }
747    }
748
749    pub fn constrain(self, size: Size) -> Size {
750        Size::new(
751            clamp_axis(size.width, self.min.width, self.max.width),
752            clamp_axis(size.height, self.min.height, self.max.height),
753        )
754    }
755
756    pub fn is_tight(self) -> bool {
757        self.min == self.max
758    }
759
760    pub fn is_bounded(self) -> bool {
761        self.max.width.is_finite() && self.max.height.is_finite()
762    }
763
764    pub fn with_width(self, min: f32, max: f32) -> Self {
765        Self {
766            min: Size::new(min, self.min.height),
767            max: Size::new(max, self.max.height),
768        }
769    }
770
771    pub fn with_height(self, min: f32, max: f32) -> Self {
772        Self {
773            min: Size::new(self.min.width, min),
774            max: Size::new(self.max.width, max),
775        }
776    }
777}
778
779fn clamp_axis(value: f32, min: f32, max: f32) -> f32 {
780    let (min, max) = if min > max { (max, max) } else { (min, max) };
781    if value < min {
782        min
783    } else if value > max {
784        max
785    } else {
786        value
787    }
788}
789
790#[cfg(test)]
791mod tests {
792    use super::*;
793
794    fn rect(x: f32, y: f32, w: f32, h: f32) -> Rect {
795        Rect::from_xywh(x, y, w, h)
796    }
797
798    #[test]
799    fn point_arithmetic() {
800        let a = Point::new(2.0, 3.0);
801        let b = Point::new(5.0, 7.0);
802        assert_eq!(b - a, Vec2::new(3.0, 4.0));
803        assert_eq!(a + Vec2::new(1.0, 1.0), Point::new(3.0, 4.0));
804        assert_eq!(a.distance(Point::ZERO), (13.0f32).sqrt());
805    }
806
807    #[test]
808    fn vec_math() {
809        let v = Vec2::new(3.0, 4.0);
810        assert_eq!(v.length(), 5.0);
811        assert_eq!(v.dot(Vec2::new(1.0, 0.0)), 3.0);
812        assert_eq!(Vec2::ZERO.normalized(), Vec2::ZERO);
813        assert_eq!(-v, Vec2::new(-3.0, -4.0));
814    }
815
816    #[test]
817    fn rect_ltrb_and_contains() {
818        let r = rect(10.0, 20.0, 30.0, 40.0);
819        assert_eq!(r.left(), 10.0);
820        assert_eq!(r.right(), 40.0);
821        assert_eq!(r.top(), 20.0);
822        assert_eq!(r.bottom(), 60.0);
823
824        assert!(r.contains(Point::new(39.9, 59.9)));
825        assert!(!r.contains(Point::new(40.0, 59.9)));
826        assert!(!r.contains(Point::new(39.9, 60.0)));
827        assert!(!r.contains(Point::new(9.9, 20.0)));
828    }
829
830    #[test]
831    fn rect_intersect() {
832        let a = rect(0.0, 0.0, 10.0, 10.0);
833        let touching = rect(10.0, 0.0, 10.0, 10.0);
834        assert!(!a.intersects(touching));
835
836        let overlap = rect(5.0, 5.0, 10.0, 10.0);
837        assert_eq!(a.intersect(overlap), rect(5.0, 5.0, 5.0, 5.0));
838
839        let disjoint = rect(100.0, 100.0, 1.0, 1.0);
840        assert!(a.intersect(disjoint).is_empty());
841        assert!(!a.intersects(disjoint));
842    }
843
844    #[test]
845    fn rect_union() {
846        let a = rect(0.0, 0.0, 10.0, 10.0);
847        let b = rect(20.0, 20.0, 10.0, 10.0);
848        assert_eq!(a.union(b), rect(0.0, 0.0, 30.0, 30.0));
849
850        let empty = rect(5.0, 5.0, 0.0, 0.0);
851        assert_eq!(empty.union(a), a);
852        assert_eq!(a.union(empty), a);
853    }
854
855    #[test]
856    fn rect_contains_rect_and_inflate() {
857        let outer = rect(0.0, 0.0, 10.0, 10.0);
858        assert!(outer.contains_rect(rect(2.0, 2.0, 3.0, 3.0)));
859        assert!(!outer.contains_rect(rect(9.0, 9.0, 5.0, 5.0)));
860
861        let inflated = outer.inflate(1.0, 2.0);
862        assert_eq!(inflated.origin, Point::new(-1.0, -2.0));
863        assert_eq!(inflated.size, Size::new(12.0, 14.0));
864
865        assert_eq!(
866            outer.translate(Vec2::new(5.0, -5.0)),
867            rect(5.0, -5.0, 10.0, 10.0)
868        );
869    }
870
871    #[test]
872    fn edges_sums() {
873        let e = Edges::uniform(2.0);
874        assert_eq!(e.horizontal_sum(), 4.0);
875        assert_eq!(e.vertical_sum(), 4.0);
876        assert_eq!(Edges::horizontal(3.0).vertical_sum(), 0.0);
877    }
878
879    #[test]
880    fn edges_ergonomics() {
881        assert_eq!(Edges::all(4.0), Edges::uniform(4.0));
882        assert_eq!(Edges::xy(10.0, 6.0), Edges::symmetric(10.0, 6.0));
883        assert_eq!(Edges::xy(10.0, 6.0), Edges::only(10.0, 6.0, 10.0, 6.0));
884        assert_eq!(Edges::from((10.0f32, 6.0f32)), Edges::xy(10.0, 6.0));
885        assert_eq!(Edges::from([10.0f32, 6.0f32]), Edges::xy(10.0, 6.0));
886        assert_eq!(
887            Edges::from([1.0f32, 2.0, 3.0, 4.0]),
888            Edges::only(1.0, 2.0, 3.0, 4.0)
889        );
890        assert_eq!(
891            Edges::from((1.0f32, 2.0, 3.0, 4.0)),
892            Edges::only(1.0, 2.0, 3.0, 4.0)
893        );
894        assert_eq!(
895            Edges::trbl(1.0, 2.0, 3.0, 4.0),
896            Edges::only(4.0, 1.0, 2.0, 3.0)
897        );
898        assert_eq!(Edges::from(8), Edges::all(8.0));
899        assert_eq!(
900            Edges::left(5.0).with_top(3.0),
901            Edges::only(5.0, 3.0, 0.0, 0.0)
902        );
903        assert!(Edges::ZERO.is_zero());
904        assert!(!Edges::all(1.0).is_zero());
905        assert_eq!(
906            Edges::all(2.0) + Edges::xy(1.0, 3.0),
907            Edges::only(3.0, 5.0, 3.0, 5.0)
908        );
909    }
910
911    #[test]
912    fn length_resolve() {
913        assert_eq!(Length::from(12.5), Length::Px(12.5));
914        assert_eq!(Length::Px(100.0).resolve(1000.0), Some(100.0));
915        assert_eq!(Length::Percent(50.0).resolve(200.0), Some(100.0));
916        assert_eq!(Length::Fill(1.0).resolve(1000.0), None);
917        assert_eq!(Length::FIT.resolve(1000.0), None);
918        assert_eq!(Length::px(-5.0), Length::Px(0.0));
919    }
920
921    #[test]
922    fn length_ext_sugar() {
923        assert_eq!(100.px(), Length::Px(100.0));
924        assert_eq!(50.percent(), Length::Percent(50.0));
925        assert_eq!(1.0.fill(), Length::Fill(1.0));
926    }
927
928    #[test]
929    fn constraints_constrain() {
930        assert_eq!(
931            Constraints::UNBOUNDED.constrain(Size::new(1e9, 1e9)),
932            Size::new(1e9, 1e9)
933        );
934        assert_eq!(
935            Constraints::tight(Size::new(50.0, 50.0)).constrain(Size::new(1.0, 999.0)),
936            Size::new(50.0, 50.0)
937        );
938        assert_eq!(
939            Constraints::loose(Size::new(100.0, 100.0)).constrain(Size::new(150.0, 50.0)),
940            Size::new(100.0, 50.0)
941        );
942
943        let c = Constraints {
944            min: Size::new(50.0, 50.0),
945            max: Size::new(100.0, 100.0),
946        };
947        assert_eq!(c.constrain(Size::new(10.0, 500.0)), Size::new(50.0, 100.0));
948
949        let inverted = Constraints {
950            min: Size::new(100.0, 100.0),
951            max: Size::new(50.0, 50.0),
952        };
953        assert_eq!(
954            inverted.constrain(Size::new(75.0, 75.0)),
955            Size::new(50.0, 50.0)
956        );
957    }
958
959    #[test]
960    fn constraints_builders() {
961        let c = Constraints::UNBOUNDED.with_width(10.0, 20.0);
962        assert_eq!(c.constrain(Size::new(100.0, 100.0)), Size::new(20.0, 100.0));
963        assert!(!c.is_tight());
964        assert!(!c.is_bounded());
965        assert!(c.loosen().min == Size::ZERO);
966        assert!(Constraints::tight(Size::new(4.0, 4.0)).is_tight());
967    }
968
969    #[test]
970    fn transform_identity_and_compose() {
971        let p = Point::new(3.0, -2.0);
972        assert_eq!(Transform::IDENTITY.apply(p), p);
973        assert!(Transform::IDENTITY.is_identity());
974        let t = Transform::translate(5.0, -1.0).then(Transform::translate(-5.0, 1.0));
975        assert!(t.is_identity(), "offsets cancel: {t:?}");
976        let r = Transform::rotate_around_degrees(90.0, Point::new(5.0, 5.0));
977        let q = r.apply(Point::new(10.0, 5.0));
978        assert!((q.x - 5.0).abs() < 1e-4, "x={}", q.x);
979        assert!((q.y - 10.0).abs() < 1e-4, "y={}", q.y);
980    }
981
982    #[test]
983    fn transform_aabb_of_rotated_rect() {
984        let r = Rect::from_xywh(0.0, 0.0, 10.0, 10.0);
985        let t = Transform::rotate_around_degrees(45.0, r.center());
986        let aabb = t.apply_rect_aabb(r);
987        let expected = 10.0 * std::f32::consts::SQRT_2;
988        assert!((aabb.size.width - expected).abs() < 0.05, "{aabb:?}");
989        assert!((aabb.size.height - expected).abs() < 0.05, "{aabb:?}");
990    }
991}