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}