use crate::kurbo::Size;
use crate::widget::Axis;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BoxConstraints {
min: Size,
max: Size,
}
impl BoxConstraints {
pub const UNBOUNDED: BoxConstraints = BoxConstraints {
min: Size::ZERO,
max: Size::new(f64::INFINITY, f64::INFINITY),
};
pub fn new(min: Size, max: Size) -> BoxConstraints {
BoxConstraints {
min: min.expand(),
max: max.expand(),
}
}
pub fn tight(size: Size) -> BoxConstraints {
let size = size.expand();
BoxConstraints {
min: size,
max: size,
}
}
pub fn loosen(&self) -> BoxConstraints {
BoxConstraints {
min: Size::ZERO,
max: self.max,
}
}
pub fn constrain(&self, size: impl Into<Size>) -> Size {
size.into().expand().clamp(self.min, self.max)
}
pub fn max(&self) -> Size {
self.max
}
pub fn min(&self) -> Size {
self.min
}
pub fn is_width_bounded(&self) -> bool {
self.max.width.is_finite()
}
pub fn is_height_bounded(&self) -> bool {
self.max.height.is_finite()
}
pub fn debug_check(&self, name: &str) {
if !(0.0 <= self.min.width
&& self.min.width <= self.max.width
&& 0.0 <= self.min.height
&& self.min.height <= self.max.height
&& self.min.expand() == self.min
&& self.max.expand() == self.max)
{
tracing::warn!("Bad BoxConstraints passed to {}:", name);
tracing::warn!("{:?}", self);
}
if self.min.width.is_infinite() {
tracing::warn!("Infinite minimum width constraint passed to {}:", name);
}
if self.min.height.is_infinite() {
tracing::warn!("Infinite minimum height constraint passed to {}:", name);
}
}
pub fn shrink(&self, diff: impl Into<Size>) -> BoxConstraints {
let diff = diff.into().expand();
let min = Size::new(
(self.min().width - diff.width).max(0.),
(self.min().height - diff.height).max(0.),
);
let max = Size::new(
(self.max().width - diff.width).max(0.),
(self.max().height - diff.height).max(0.),
);
BoxConstraints::new(min, max)
}
pub fn contains(&self, size: impl Into<Size>) -> bool {
let size = size.into();
(self.min.width <= size.width && size.width <= self.max.width)
&& (self.min.height <= size.height && size.height <= self.max.height)
}
pub fn constrain_aspect_ratio(&self, aspect_ratio: f64, width: f64) -> Size {
let ideal_size = Size {
width,
height: width * aspect_ratio,
};
let aspect_ratio = aspect_ratio.abs();
let width = width.abs();
if self.contains(ideal_size) {
return ideal_size;
}
let min_w_min_h = self.min.height / self.min.width;
let max_w_min_h = self.min.height / self.max.width;
let min_w_max_h = self.max.height / self.min.width;
let max_w_max_h = self.max.height / self.max.width;
if aspect_ratio > min_w_max_h {
Size {
width: self.min.width,
height: self.max.height,
}
} else if aspect_ratio < max_w_min_h {
Size {
width: self.max.width,
height: self.min.height,
}
} else if aspect_ratio > min_w_min_h {
if width < self.min.width {
Size {
width: self.min.width,
height: self.min.width * aspect_ratio,
}
} else if aspect_ratio < max_w_max_h {
Size {
width: self.max.width,
height: self.max.width * aspect_ratio,
}
} else {
Size {
width: self.max.height * aspect_ratio.recip(),
height: self.max.height,
}
}
} else {
if width < self.min.width {
Size {
width: self.min.height * aspect_ratio.recip(),
height: self.min.height,
}
} else if aspect_ratio > max_w_max_h {
Size {
width: self.max.height * aspect_ratio.recip(),
height: self.max.height,
}
} else {
Size {
width: self.max.width,
height: self.max.width * aspect_ratio,
}
}
}
}
pub fn unbound_max(&self, axis: Axis) -> Self {
match axis {
Axis::Horizontal => self.unbound_max_width(),
Axis::Vertical => self.unbound_max_height(),
}
}
pub fn unbound_max_width(&self) -> Self {
let mut max = self.max();
max.width = f64::INFINITY;
BoxConstraints::new(self.min(), max)
}
pub fn unbound_max_height(&self) -> Self {
let mut max = self.max();
max.height = f64::INFINITY;
BoxConstraints::new(self.min(), max)
}
pub fn shrink_max_to(&self, axis: Axis, dim: f64) -> Self {
match axis {
Axis::Horizontal => self.shrink_max_width_to(dim),
Axis::Vertical => self.shrink_max_height_to(dim),
}
}
pub fn shrink_max_width_to(&self, dim: f64) -> Self {
let mut max = self.max();
max.width = f64::max(dim, self.min().width);
BoxConstraints::new(self.min(), max)
}
pub fn shrink_max_height_to(&self, dim: f64) -> Self {
let mut max = self.max();
max.height = f64::max(dim, self.min().height);
BoxConstraints::new(self.min(), max)
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
fn bc(min_width: f64, min_height: f64, max_width: f64, max_height: f64) -> BoxConstraints {
BoxConstraints::new(
Size::new(min_width, min_height),
Size::new(max_width, max_height),
)
}
#[test]
fn constrain_aspect_ratio() {
for (bc, aspect_ratio, width, output) in [
(bc(0.0, 0.0, 100.0, 100.0), 1.0, 50.0, Size::new(50.0, 50.0)),
(bc(0.0, 10.0, 90.0, 100.0), 1.0, 50.0, Size::new(50.0, 50.0)),
(
bc(10.0, 10.0, 100.0, 100.0),
1.0,
5.0,
Size::new(10.0, 10.0),
),
(
bc(40.0, 90.0, 60.0, 100.0),
2.0,
30.0,
Size::new(45.0, 90.0),
),
(
bc(10.0, 10.0, 100.0, 100.0),
0.5,
5.0,
Size::new(20.0, 10.0),
),
(
bc(10.0, 10.0, 100.0, 100.0),
2.0,
5.0,
Size::new(10.0, 20.0),
),
(
bc(90.0, 40.0, 100.0, 60.0),
0.5,
60.0,
Size::new(90.0, 45.0),
),
(
bc(50.0, 0.0, 50.0, 100.0),
1.0,
100.0,
Size::new(50.0, 50.0),
),
(
bc(10.0, 10.0, 100.0, 100.0),
2.0,
105.0,
Size::new(50.0, 100.0),
),
(
bc(10.0, 10.0, 100.0, 100.0),
0.5,
105.0,
Size::new(100.0, 50.0),
),
(
bc(20.0, 20.0, 40.0, 40.0),
10.0,
30.0,
Size::new(20.0, 40.0),
),
(bc(20.0, 20.0, 40.0, 40.0), 0.1, 30.0, Size::new(40.0, 20.0)),
(
bc(50.0, 0.0, 50.0, f64::INFINITY),
1.0,
100.0,
Size::new(50.0, 50.0),
),
]
.iter()
{
assert_eq!(
bc.constrain_aspect_ratio(*aspect_ratio, *width),
*output,
"bc:{bc:?}, ar:{aspect_ratio}, w:{width}"
);
}
}
#[test]
fn unbounded() {
assert!(!BoxConstraints::UNBOUNDED.is_width_bounded());
assert!(!BoxConstraints::UNBOUNDED.is_height_bounded());
assert_eq!(BoxConstraints::UNBOUNDED.min(), Size::ZERO);
}
}