1#[derive(Clone, Copy, Debug, PartialEq)]
5pub struct Constraints {
6 pub min_width: f32,
7 pub max_width: f32,
8 pub min_height: f32,
9 pub max_height: f32,
10}
11
12impl Constraints {
13 pub fn tight(width: f32, height: f32) -> Self {
15 Self {
16 min_width: width,
17 max_width: width,
18 min_height: height,
19 max_height: height,
20 }
21 }
22
23 pub fn loose(max_width: f32, max_height: f32) -> Self {
25 Self {
26 min_width: 0.0,
27 max_width,
28 min_height: 0.0,
29 max_height,
30 }
31 }
32
33 pub fn is_tight(&self) -> bool {
35 self.min_width == self.max_width && self.min_height == self.max_height
36 }
37
38 pub fn is_bounded(&self) -> bool {
40 self.max_width.is_finite() && self.max_height.is_finite()
41 }
42
43 pub fn constrain(&self, width: f32, height: f32) -> (f32, f32) {
45 (
46 width.clamp(self.min_width, self.max_width),
47 height.clamp(self.min_height, self.max_height),
48 )
49 }
50
51 #[inline]
53 pub fn has_bounded_width(&self) -> bool {
54 self.max_width.is_finite()
55 }
56
57 #[inline]
59 pub fn has_bounded_height(&self) -> bool {
60 self.max_height.is_finite()
61 }
62
63 #[inline]
65 pub fn has_tight_width(&self) -> bool {
66 self.min_width == self.max_width
67 }
68
69 #[inline]
71 pub fn has_tight_height(&self) -> bool {
72 self.min_height == self.max_height
73 }
74
75 pub fn tighten_width(self, width: f32) -> Self {
77 Self {
78 min_width: width,
79 max_width: width,
80 ..self
81 }
82 }
83
84 pub fn tighten_height(self, height: f32) -> Self {
86 Self {
87 min_height: height,
88 max_height: height,
89 ..self
90 }
91 }
92
93 pub fn copy_with_width(self, min_width: f32, max_width: f32) -> Self {
95 Self {
96 min_width,
97 max_width,
98 ..self
99 }
100 }
101
102 pub fn copy_with_height(self, min_height: f32, max_height: f32) -> Self {
104 Self {
105 min_height,
106 max_height,
107 ..self
108 }
109 }
110
111 pub fn deflate(self, horizontal: f32, vertical: f32) -> Self {
114 Self {
115 min_width: (self.min_width - horizontal).max(0.0),
116 max_width: (self.max_width - horizontal).max(0.0),
117 min_height: (self.min_height - vertical).max(0.0),
118 max_height: (self.max_height - vertical).max(0.0),
119 }
120 }
121
122 pub fn loosen(self) -> Self {
124 Self {
125 min_width: 0.0,
126 min_height: 0.0,
127 ..self
128 }
129 }
130
131 pub fn enforce(self, width: f32, height: f32) -> Self {
133 Self {
134 min_width: width.clamp(self.min_width, self.max_width),
135 max_width: width.clamp(self.min_width, self.max_width),
136 min_height: height.clamp(self.min_height, self.max_height),
137 max_height: height.clamp(self.min_height, self.max_height),
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 fn bounds() -> Constraints {
147 Constraints {
148 min_width: 20.0,
149 max_width: 200.0,
150 min_height: 10.0,
151 max_height: 100.0,
152 }
153 }
154
155 #[test]
156 fn a_constraint_reports_which_of_its_axes_are_bounded() {
157 assert!(bounds().has_bounded_width());
158 assert!(bounds().has_bounded_height());
159
160 let scrolling = bounds().copy_with_height(0.0, f32::INFINITY);
161 assert!(scrolling.has_bounded_width());
162 assert!(!scrolling.has_bounded_height());
163 assert!(!scrolling.is_bounded());
164 }
165
166 #[test]
167 fn a_constraint_reports_which_of_its_axes_are_tight() {
168 let loose = Constraints::loose(200.0, 100.0);
169 assert!(!loose.has_tight_width());
170 assert!(!loose.has_tight_height());
171 assert!(!loose.is_tight());
172
173 let tight = Constraints::tight(200.0, 100.0);
174 assert!(tight.has_tight_width());
175 assert!(tight.has_tight_height());
176 assert!(tight.is_tight());
177
178 let row_child = loose.tighten_height(100.0);
179 assert!(!row_child.has_tight_width());
180 assert!(row_child.has_tight_height());
181 assert!(!row_child.is_tight());
182 }
183
184 #[test]
185 fn tightening_one_axis_leaves_the_other_alone() {
186 let tightened = bounds().tighten_width(64.0);
187 assert_eq!(tightened.min_width, 64.0);
188 assert_eq!(tightened.max_width, 64.0);
189 assert_eq!(tightened.min_height, 10.0);
190 assert_eq!(tightened.max_height, 100.0);
191
192 let tightened = bounds().tighten_height(64.0);
193 assert_eq!(tightened.min_height, 64.0);
194 assert_eq!(tightened.max_height, 64.0);
195 assert_eq!(tightened.min_width, 20.0);
196 assert_eq!(tightened.max_width, 200.0);
197 }
198
199 #[test]
200 fn replacing_one_axis_leaves_the_other_alone() {
201 let replaced = bounds().copy_with_width(0.0, 50.0);
202 assert_eq!((replaced.min_width, replaced.max_width), (0.0, 50.0));
203 assert_eq!((replaced.min_height, replaced.max_height), (10.0, 100.0));
204
205 let replaced = bounds().copy_with_height(5.0, 50.0);
206 assert_eq!((replaced.min_height, replaced.max_height), (5.0, 50.0));
207 assert_eq!((replaced.min_width, replaced.max_width), (20.0, 200.0));
208 }
209
210 #[test]
211 fn deflating_takes_padding_off_both_ends_and_stops_at_zero() {
212 let inner = bounds().deflate(16.0, 8.0);
213 assert_eq!(inner.min_width, 4.0);
214 assert_eq!(inner.max_width, 184.0);
215 assert_eq!(inner.min_height, 2.0);
216 assert_eq!(inner.max_height, 92.0);
217
218 let squeezed = Constraints::tight(10.0, 10.0).deflate(40.0, 40.0);
219 assert_eq!(squeezed.min_width, 0.0);
220 assert_eq!(squeezed.max_width, 0.0);
221 assert_eq!(squeezed.min_height, 0.0);
222 assert_eq!(squeezed.max_height, 0.0);
223 }
224
225 #[test]
226 fn loosening_drops_the_minimums_and_keeps_the_maximums() {
227 let loosened = Constraints::tight(200.0, 100.0).loosen();
228 assert_eq!(loosened.min_width, 0.0);
229 assert_eq!(loosened.min_height, 0.0);
230 assert_eq!(loosened.max_width, 200.0);
231 assert_eq!(loosened.max_height, 100.0);
232 assert!(!loosened.is_tight());
233 }
234}