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