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 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}