1use crate::geometry::Size;
19use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
39pub struct Constraints {
40 pub min_width: f32,
42 pub max_width: f32,
44 pub min_height: f32,
46 pub max_height: f32,
48}
49
50impl Constraints {
51 #[must_use]
53 pub const fn new(min_width: f32, max_width: f32, min_height: f32, max_height: f32) -> Self {
54 Self {
55 min_width,
56 max_width,
57 min_height,
58 max_height,
59 }
60 }
61
62 #[must_use]
64 pub const fn tight(size: Size) -> Self {
65 Self::new(size.width, size.width, size.height, size.height)
66 }
67
68 #[must_use]
70 pub const fn loose(size: Size) -> Self {
71 Self::new(0.0, size.width, 0.0, size.height)
72 }
73
74 #[must_use]
76 pub const fn unbounded() -> Self {
77 Self::new(0.0, f32::INFINITY, 0.0, f32::INFINITY)
78 }
79
80 #[must_use]
82 pub fn constrain(&self, size: Size) -> Size {
83 Size::new(
84 size.width.clamp(self.min_width, self.max_width),
85 size.height.clamp(self.min_height, self.max_height),
86 )
87 }
88
89 #[must_use]
91 pub fn is_tight(&self) -> bool {
92 self.min_width == self.max_width && self.min_height == self.max_height
93 }
94
95 #[must_use]
97 pub fn has_bounded_width(&self) -> bool {
98 self.max_width.is_finite()
99 }
100
101 #[must_use]
103 pub fn has_bounded_height(&self) -> bool {
104 self.max_height.is_finite()
105 }
106
107 #[must_use]
109 pub fn is_bounded(&self) -> bool {
110 self.has_bounded_width() && self.has_bounded_height()
111 }
112
113 #[must_use]
115 pub fn biggest(&self) -> Size {
116 Size::new(
117 if self.max_width.is_finite() {
118 self.max_width
119 } else {
120 self.min_width
121 },
122 if self.max_height.is_finite() {
123 self.max_height
124 } else {
125 self.min_height
126 },
127 )
128 }
129
130 #[must_use]
132 pub const fn smallest(&self) -> Size {
133 Size::new(self.min_width, self.min_height)
134 }
135
136 #[must_use]
138 pub const fn with_min_width(&self, min_width: f32) -> Self {
139 Self::new(min_width, self.max_width, self.min_height, self.max_height)
140 }
141
142 #[must_use]
144 pub const fn with_max_width(&self, max_width: f32) -> Self {
145 Self::new(self.min_width, max_width, self.min_height, self.max_height)
146 }
147
148 #[must_use]
150 pub const fn with_min_height(&self, min_height: f32) -> Self {
151 Self::new(self.min_width, self.max_width, min_height, self.max_height)
152 }
153
154 #[must_use]
156 pub const fn with_max_height(&self, max_height: f32) -> Self {
157 Self::new(self.min_width, self.max_width, self.min_height, max_height)
158 }
159
160 #[must_use]
162 pub fn deflate(&self, horizontal: f32, vertical: f32) -> Self {
163 Self::new(
164 (self.min_width - horizontal).max(0.0),
165 (self.max_width - horizontal).max(0.0),
166 (self.min_height - vertical).max(0.0),
167 (self.max_height - vertical).max(0.0),
168 )
169 }
170}
171
172impl Default for Constraints {
173 fn default() -> Self {
174 Self::unbounded()
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_constraints_default() {
184 let c = Constraints::default();
185 assert_eq!(c.min_width, 0.0);
186 assert_eq!(c.max_width, f32::INFINITY);
187 }
188
189 #[test]
190 fn test_constraints_biggest() {
191 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
192 assert_eq!(c.biggest(), Size::new(100.0, 200.0));
193 }
194
195 #[test]
196 fn test_constraints_smallest() {
197 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
198 assert_eq!(c.smallest(), Size::new(10.0, 20.0));
199 }
200
201 #[test]
202 fn test_constraints_deflate() {
203 let c = Constraints::new(100.0, 200.0, 100.0, 200.0);
204 let deflated = c.deflate(20.0, 20.0);
205 assert_eq!(deflated.min_width, 80.0);
206 assert_eq!(deflated.max_width, 180.0);
207 }
208
209 #[test]
210 fn test_constraints_with_methods() {
211 let c = Constraints::new(0.0, 100.0, 0.0, 100.0);
212 assert_eq!(c.with_min_width(10.0).min_width, 10.0);
213 assert_eq!(c.with_max_width(200.0).max_width, 200.0);
214 assert_eq!(c.with_min_height(10.0).min_height, 10.0);
215 assert_eq!(c.with_max_height(200.0).max_height, 200.0);
216 }
217
218 #[test]
219 fn test_constraints_tight() {
220 let c = Constraints::tight(Size::new(100.0, 50.0));
221 assert_eq!(c.min_width, 100.0);
222 assert_eq!(c.max_width, 100.0);
223 assert_eq!(c.min_height, 50.0);
224 assert_eq!(c.max_height, 50.0);
225 assert!(c.is_tight());
226 }
227
228 #[test]
229 fn test_constraints_loose() {
230 let c = Constraints::loose(Size::new(100.0, 50.0));
231 assert_eq!(c.min_width, 0.0);
232 assert_eq!(c.max_width, 100.0);
233 assert_eq!(c.min_height, 0.0);
234 assert_eq!(c.max_height, 50.0);
235 assert!(!c.is_tight());
236 }
237
238 #[test]
239 fn test_constraints_unbounded() {
240 let c = Constraints::unbounded();
241 assert_eq!(c.min_width, 0.0);
242 assert!(c.max_width.is_infinite());
243 assert!(!c.is_bounded());
244 }
245
246 #[test]
247 fn test_constraints_constrain() {
248 let c = Constraints::new(10.0, 100.0, 20.0, 80.0);
249 assert_eq!(c.constrain(Size::new(50.0, 50.0)), Size::new(50.0, 50.0));
250 assert_eq!(c.constrain(Size::new(5.0, 5.0)), Size::new(10.0, 20.0));
251 assert_eq!(c.constrain(Size::new(200.0, 200.0)), Size::new(100.0, 80.0));
252 }
253
254 #[test]
255 fn test_constraints_is_tight_false() {
256 let c = Constraints::new(0.0, 100.0, 0.0, 100.0);
257 assert!(!c.is_tight());
258 }
259
260 #[test]
261 fn test_constraints_has_bounded_width() {
262 let c = Constraints::new(0.0, 100.0, 0.0, f32::INFINITY);
263 assert!(c.has_bounded_width());
264 assert!(!c.has_bounded_height());
265 }
266
267 #[test]
268 fn test_constraints_is_bounded() {
269 let bounded = Constraints::new(0.0, 100.0, 0.0, 100.0);
270 assert!(bounded.is_bounded());
271
272 let unbounded = Constraints::unbounded();
273 assert!(!unbounded.is_bounded());
274 }
275
276 #[test]
277 fn test_constraints_biggest_unbounded() {
278 let c = Constraints::unbounded();
279 assert_eq!(c.biggest(), Size::new(0.0, 0.0));
280 }
281
282 #[test]
283 fn test_constraints_deflate_to_zero() {
284 let c = Constraints::new(10.0, 20.0, 10.0, 20.0);
285 let deflated = c.deflate(50.0, 50.0);
286 assert_eq!(deflated.min_width, 0.0);
287 assert_eq!(deflated.max_width, 0.0);
288 }
289
290 #[test]
295 fn test_constraints_clone() {
296 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
297 let cloned = c;
298 assert_eq!(c, cloned);
299 }
300
301 #[test]
302 fn test_constraints_copy() {
303 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
304 let copied = c;
305 assert_eq!(c.min_width, copied.min_width);
307 assert_eq!(c.max_width, copied.max_width);
308 }
309
310 #[test]
315 fn test_constraints_debug() {
316 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
317 let debug = format!("{:?}", c);
318 assert!(debug.contains("Constraints"));
319 assert!(debug.contains("min_width"));
320 assert!(debug.contains("max_width"));
321 }
322
323 #[test]
328 fn test_constraints_equality() {
329 let c1 = Constraints::new(10.0, 100.0, 20.0, 200.0);
330 let c2 = Constraints::new(10.0, 100.0, 20.0, 200.0);
331 assert_eq!(c1, c2);
332 }
333
334 #[test]
335 fn test_constraints_inequality_min_width() {
336 let c1 = Constraints::new(10.0, 100.0, 20.0, 200.0);
337 let c2 = Constraints::new(15.0, 100.0, 20.0, 200.0);
338 assert_ne!(c1, c2);
339 }
340
341 #[test]
342 fn test_constraints_inequality_max_width() {
343 let c1 = Constraints::new(10.0, 100.0, 20.0, 200.0);
344 let c2 = Constraints::new(10.0, 150.0, 20.0, 200.0);
345 assert_ne!(c1, c2);
346 }
347
348 #[test]
349 fn test_constraints_inequality_min_height() {
350 let c1 = Constraints::new(10.0, 100.0, 20.0, 200.0);
351 let c2 = Constraints::new(10.0, 100.0, 25.0, 200.0);
352 assert_ne!(c1, c2);
353 }
354
355 #[test]
356 fn test_constraints_inequality_max_height() {
357 let c1 = Constraints::new(10.0, 100.0, 20.0, 200.0);
358 let c2 = Constraints::new(10.0, 100.0, 20.0, 250.0);
359 assert_ne!(c1, c2);
360 }
361
362 #[test]
367 fn test_constraints_serialize() {
368 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
369 let json = serde_json::to_string(&c).unwrap();
370 assert!(json.contains("min_width"));
371 assert!(json.contains("10"));
372 }
373
374 #[test]
375 fn test_constraints_deserialize() {
376 let json = r#"{"min_width":10.0,"max_width":100.0,"min_height":20.0,"max_height":200.0}"#;
377 let c: Constraints = serde_json::from_str(json).unwrap();
378 assert_eq!(c.min_width, 10.0);
379 assert_eq!(c.max_width, 100.0);
380 assert_eq!(c.min_height, 20.0);
381 assert_eq!(c.max_height, 200.0);
382 }
383
384 #[test]
385 fn test_constraints_roundtrip_serialization() {
386 let original = Constraints::new(15.5, 150.5, 25.5, 250.5);
387 let json = serde_json::to_string(&original).unwrap();
388 let deserialized: Constraints = serde_json::from_str(&json).unwrap();
389 assert_eq!(original, deserialized);
390 }
391
392 #[test]
397 fn test_constrain_at_minimum() {
398 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
399 let size = Size::new(10.0, 20.0);
400 assert_eq!(c.constrain(size), size);
401 }
402
403 #[test]
404 fn test_constrain_at_maximum() {
405 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
406 let size = Size::new(100.0, 200.0);
407 assert_eq!(c.constrain(size), size);
408 }
409
410 #[test]
411 fn test_constrain_zero_size() {
412 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
413 let size = Size::new(0.0, 0.0);
414 assert_eq!(c.constrain(size), Size::new(10.0, 20.0));
415 }
416
417 #[test]
418 fn test_constrain_negative_clamped() {
419 let c = Constraints::new(0.0, 100.0, 0.0, 100.0);
420 let size = Size::new(-10.0, -20.0);
421 assert_eq!(c.constrain(size), Size::new(0.0, 0.0));
422 }
423
424 #[test]
425 fn test_constrain_with_zero_constraints() {
426 let c = Constraints::new(0.0, 0.0, 0.0, 0.0);
427 let size = Size::new(100.0, 100.0);
428 assert_eq!(c.constrain(size), Size::new(0.0, 0.0));
429 }
430
431 #[test]
436 fn test_is_tight_width_only() {
437 let c = Constraints::new(50.0, 50.0, 0.0, 100.0);
438 assert!(!c.is_tight()); }
440
441 #[test]
442 fn test_is_tight_height_only() {
443 let c = Constraints::new(0.0, 100.0, 50.0, 50.0);
444 assert!(!c.is_tight()); }
446
447 #[test]
448 fn test_is_tight_zero_size() {
449 let c = Constraints::tight(Size::new(0.0, 0.0));
450 assert!(c.is_tight());
451 }
452
453 #[test]
458 fn test_has_bounded_height_only() {
459 let c = Constraints::new(0.0, f32::INFINITY, 0.0, 100.0);
460 assert!(!c.has_bounded_width());
461 assert!(c.has_bounded_height());
462 assert!(!c.is_bounded());
463 }
464
465 #[test]
466 fn test_has_bounded_width_only() {
467 let c = Constraints::new(0.0, 100.0, 0.0, f32::INFINITY);
468 assert!(c.has_bounded_width());
469 assert!(!c.has_bounded_height());
470 assert!(!c.is_bounded());
471 }
472
473 #[test]
478 fn test_biggest_with_infinity_width_only() {
479 let c = Constraints::new(50.0, f32::INFINITY, 0.0, 100.0);
480 let biggest = c.biggest();
481 assert_eq!(biggest.width, 50.0); assert_eq!(biggest.height, 100.0);
483 }
484
485 #[test]
486 fn test_biggest_with_infinity_height_only() {
487 let c = Constraints::new(0.0, 100.0, 50.0, f32::INFINITY);
488 let biggest = c.biggest();
489 assert_eq!(biggest.width, 100.0);
490 assert_eq!(biggest.height, 50.0); }
492
493 #[test]
494 fn test_biggest_tight_constraints() {
495 let c = Constraints::tight(Size::new(42.0, 24.0));
496 assert_eq!(c.biggest(), Size::new(42.0, 24.0));
497 }
498
499 #[test]
504 fn test_smallest_unbounded() {
505 let c = Constraints::unbounded();
506 assert_eq!(c.smallest(), Size::new(0.0, 0.0));
507 }
508
509 #[test]
510 fn test_smallest_tight() {
511 let c = Constraints::tight(Size::new(42.0, 24.0));
512 assert_eq!(c.smallest(), Size::new(42.0, 24.0));
513 }
514
515 #[test]
516 fn test_smallest_loose() {
517 let c = Constraints::loose(Size::new(100.0, 200.0));
518 assert_eq!(c.smallest(), Size::new(0.0, 0.0));
519 }
520
521 #[test]
526 fn test_with_methods_chained() {
527 let c = Constraints::unbounded()
528 .with_min_width(10.0)
529 .with_max_width(100.0)
530 .with_min_height(20.0)
531 .with_max_height(200.0);
532
533 assert_eq!(c.min_width, 10.0);
534 assert_eq!(c.max_width, 100.0);
535 assert_eq!(c.min_height, 20.0);
536 assert_eq!(c.max_height, 200.0);
537 }
538
539 #[test]
540 fn test_with_methods_preserve_other_values() {
541 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
542
543 let c2 = c.with_min_width(15.0);
544 assert_eq!(c2.max_width, 100.0);
545 assert_eq!(c2.min_height, 20.0);
546 assert_eq!(c2.max_height, 200.0);
547
548 let c3 = c.with_max_width(150.0);
549 assert_eq!(c3.min_width, 10.0);
550 assert_eq!(c3.min_height, 20.0);
551 assert_eq!(c3.max_height, 200.0);
552 }
553
554 #[test]
559 fn test_deflate_asymmetric() {
560 let c = Constraints::new(20.0, 100.0, 30.0, 150.0);
561 let deflated = c.deflate(10.0, 20.0);
562 assert_eq!(deflated.min_width, 10.0);
563 assert_eq!(deflated.max_width, 90.0);
564 assert_eq!(deflated.min_height, 10.0);
565 assert_eq!(deflated.max_height, 130.0);
566 }
567
568 #[test]
569 fn test_deflate_zero() {
570 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
571 let deflated = c.deflate(0.0, 0.0);
572 assert_eq!(c, deflated);
573 }
574
575 #[test]
576 fn test_deflate_exact_match() {
577 let c = Constraints::new(10.0, 100.0, 20.0, 200.0);
578 let deflated = c.deflate(10.0, 20.0);
579 assert_eq!(deflated.min_width, 0.0);
580 assert_eq!(deflated.max_width, 90.0);
581 assert_eq!(deflated.min_height, 0.0);
582 assert_eq!(deflated.max_height, 180.0);
583 }
584
585 #[test]
586 fn test_deflate_negative_becomes_zero() {
587 let c = Constraints::new(5.0, 10.0, 5.0, 10.0);
588 let deflated = c.deflate(15.0, 15.0);
589 assert_eq!(deflated.min_width, 0.0);
590 assert_eq!(deflated.max_width, 0.0);
591 assert_eq!(deflated.min_height, 0.0);
592 assert_eq!(deflated.max_height, 0.0);
593 }
594
595 #[test]
600 fn test_new_with_zero_values() {
601 let c = Constraints::new(0.0, 0.0, 0.0, 0.0);
602 assert_eq!(c.min_width, 0.0);
603 assert_eq!(c.max_width, 0.0);
604 assert!(c.is_tight());
605 }
606
607 #[test]
608 fn test_tight_with_large_values() {
609 let c = Constraints::tight(Size::new(10000.0, 10000.0));
610 assert!(c.is_tight());
611 assert_eq!(c.biggest(), Size::new(10000.0, 10000.0));
612 }
613
614 #[test]
615 fn test_loose_with_zero() {
616 let c = Constraints::loose(Size::new(0.0, 0.0));
617 assert!(c.is_tight()); assert_eq!(c.biggest(), Size::new(0.0, 0.0));
619 }
620
621 #[test]
626 fn test_default_is_unbounded() {
627 let default = Constraints::default();
628 let unbounded = Constraints::unbounded();
629 assert_eq!(default, unbounded);
630 }
631
632 #[test]
633 fn test_default_not_bounded() {
634 let c = Constraints::default();
635 assert!(!c.is_bounded());
636 assert!(!c.has_bounded_width());
637 assert!(!c.has_bounded_height());
638 }
639}