163 lines
4.6 KiB
Rust
163 lines
4.6 KiB
Rust
/// 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());
|
|
}
|
|
}
|