1use crate::Rect;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5pub enum ObjectFit {
6 Fill,
8 #[default]
10 Contain,
11 Cover,
13 ContainInteger,
23}
24
25pub fn fit_rect(intrinsic: (f32, f32), container: Rect, fit: ObjectFit) -> (Rect, bool) {
30 let (iw, ih) = intrinsic;
31 if iw <= 0.0 || ih <= 0.0 || container.width <= 0.0 || container.height <= 0.0 {
33 return (container, false);
34 }
35 match fit {
36 ObjectFit::Fill => (container, false),
37 ObjectFit::Contain | ObjectFit::Cover | ObjectFit::ContainInteger => {
38 let sx = container.width / iw;
39 let sy = container.height / ih;
40 let (s, clip) = match fit {
41 ObjectFit::Cover => (sx.max(sy), true),
42 ObjectFit::ContainInteger => (sx.min(sy).floor().max(1.0).min(sx.min(sy)), false),
43 _ => (sx.min(sy), false),
44 };
45 let w = iw * s;
46 let h = ih * s;
47 let x = container.x + (container.width - w) * 0.5;
48 let y = container.y + (container.height - h) * 0.5;
49 (Rect::new(x, y, w, h), clip)
50 }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn fill_returns_container_and_no_clip() {
60 let c = Rect::new(0.0, 0.0, 120.0, 60.0);
61 let (rect, clip) = fit_rect((10.0, 10.0), c, ObjectFit::Fill);
62 assert_eq!(rect, c);
63 assert!(!clip);
64 }
65
66 #[test]
67 fn contain_letterboxes_wide_box() {
68 let c = Rect::new(0.0, 0.0, 120.0, 60.0);
70 let (rect, clip) = fit_rect((10.0, 10.0), c, ObjectFit::Contain);
71 assert_eq!(rect, Rect::new(30.0, 0.0, 60.0, 60.0));
72 assert!(!clip);
73 }
74
75 #[test]
76 fn cover_overflows_and_clips() {
77 let c = Rect::new(0.0, 0.0, 120.0, 60.0);
79 let (rect, clip) = fit_rect((10.0, 10.0), c, ObjectFit::Cover);
80 assert_eq!(rect, Rect::new(0.0, -30.0, 120.0, 120.0));
81 assert!(clip);
82 }
83
84 #[test]
85 fn contain_respects_container_origin() {
86 let c = Rect::new(5.0, 7.0, 120.0, 60.0);
87 let (rect, _) = fit_rect((10.0, 10.0), c, ObjectFit::Contain);
88 assert_eq!(rect, Rect::new(35.0, 7.0, 60.0, 60.0));
89 }
90
91 #[test]
92 fn degenerate_intrinsic_fills() {
93 let c = Rect::new(0.0, 0.0, 120.0, 60.0);
94 let (rect, clip) = fit_rect((0.0, 10.0), c, ObjectFit::Contain);
95 assert_eq!(rect, c);
96 assert!(!clip);
97 }
98
99 #[test]
100 fn default_is_contain() {
101 assert_eq!(ObjectFit::default(), ObjectFit::Contain);
102 }
103
104 #[test]
107 fn contain_integer_floors_the_scale_and_widens_the_letterbox() {
108 let c = Rect::new(0.0, 0.0, 1300.0, 740.0);
110 let (rect, clip) = fit_rect((320.0, 180.0), c, ObjectFit::ContainInteger);
111 assert_eq!(rect, Rect::new(10.0, 10.0, 1280.0, 720.0));
112 assert!(!clip);
113 }
114
115 #[test]
117 fn contain_integer_fills_an_exact_multiple() {
118 let c = Rect::new(0.0, 0.0, 1280.0, 720.0);
119 let (rect, _) = fit_rect((320.0, 180.0), c, ObjectFit::ContainInteger);
120 assert_eq!(rect, Rect::new(0.0, 0.0, 1280.0, 720.0));
121 }
122
123 #[test]
125 fn contain_integer_below_one_falls_back_to_fitting() {
126 let c = Rect::new(0.0, 0.0, 160.0, 90.0);
127 let (rect, _) = fit_rect((320.0, 180.0), c, ObjectFit::ContainInteger);
128 assert_eq!(rect, Rect::new(0.0, 0.0, 160.0, 90.0));
129 }
130}