/// Layout constraints — what the parent is offering the child. /// /// A parent passes a LayoutConstraints to each child during layout. /// The child must produce a size within these constraints. /// Constraints flow down; sizes flow back up. /// Constraints passed from a parent to a child during layout. #[derive(Debug, Clone, Copy, PartialEq)] pub struct LayoutConstraints { /// Minimum width the child must be (dp). pub min_width: f32, /// Maximum width available to the child (dp). f32::INFINITY = unbounded. pub max_width: f32, /// Minimum height the child must be (dp). pub min_height: f32, /// Maximum height available to the child (dp). f32::INFINITY = unbounded. pub max_height: f32, } impl LayoutConstraints { /// Unconstrained — the child can be any size. pub fn unbounded() -> Self { Self { min_width: 0.0, max_width: f32::INFINITY, min_height: 0.0, max_height: f32::INFINITY, } } /// Constrained to a specific width, unconstrained height. pub fn with_max_width(max_width: f32) -> Self { Self { min_width: 0.0, max_width, min_height: 0.0, max_height: f32::INFINITY, } } /// Constrained to an exact width and height. pub fn tight(width: f32, height: f32) -> Self { Self { min_width: width, max_width: width, min_height: height, max_height: height, } } /// Return constraints loosened to allow any size up to the maximums. pub fn loosen(&self) -> Self { Self { min_width: 0.0, max_width: self.max_width, min_height: 0.0, max_height: self.max_height, } } /// Deflate the constraints by padding amounts. /// Useful when a parent applies its own padding before offering space to a child. pub fn deflate(&self, horizontal: f32, vertical: f32) -> Self { Self { min_width: (self.min_width - horizontal).max(0.0), max_width: (self.max_width - horizontal).max(0.0), min_height: (self.min_height - vertical).max(0.0), max_height: (self.max_height - vertical).max(0.0), } } /// Is the width dimension bounded? pub fn has_bounded_width(&self) -> bool { self.max_width.is_finite() } /// Is the height dimension bounded? pub fn has_bounded_height(&self) -> bool { self.max_height.is_finite() } /// Clamp a proposed size to fit within these constraints. pub fn clamp_size(&self, width: f32, height: f32) -> (f32, f32) { ( width.clamp(self.min_width, self.max_width), height.clamp(self.min_height, self.max_height), ) } } /// The size a child reports back to its parent after layout. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Size { pub width: f32, pub height: f32, } impl Size { pub fn new(width: f32, height: f32) -> Self { Self { width, height } } pub fn zero() -> Self { Self { width: 0.0, height: 0.0 } } } #[cfg(test)] mod tests { use super::*; #[test] fn unbounded_has_no_max() { let c = LayoutConstraints::unbounded(); assert!(c.max_width.is_infinite()); assert!(c.max_height.is_infinite()); } #[test] fn tight_constraints() { let c = LayoutConstraints::tight(100.0, 50.0); assert_eq!(c.min_width, 100.0); assert_eq!(c.max_width, 100.0); } #[test] fn deflate_reduces_available_space() { let c = LayoutConstraints::tight(200.0, 100.0); let deflated = c.deflate(16.0, 8.0); assert_eq!(deflated.max_width, 184.0); assert_eq!(deflated.max_height, 92.0); } #[test] fn deflate_does_not_go_negative() { let c = LayoutConstraints::tight(10.0, 10.0); let deflated = c.deflate(20.0, 20.0); assert_eq!(deflated.max_width, 0.0); assert_eq!(deflated.max_height, 0.0); } #[test] fn clamp_size() { let c = LayoutConstraints { min_width: 50.0, max_width: 200.0, min_height: 30.0, max_height: 100.0, }; let (w, h) = c.clamp_size(250.0, 20.0); assert_eq!(w, 200.0); assert_eq!(h, 30.0); } #[test] fn bounded_width_detection() { let bounded = LayoutConstraints::with_max_width(400.0); let unbounded = LayoutConstraints::unbounded(); assert!(bounded.has_bounded_width()); assert!(!unbounded.has_bounded_width()); } }