#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LayoutConstraints {
pub min_w: Length,
pub min_h: Length,
pub max_w: Option<Length>,
pub max_h: Option<Length>,
pub focus_min_w: u16,
pub focus_min_h: u16,
pub collapse_w: Option<u16>,
pub collapse_h: Option<u16>,
pub force_compact: bool,
pub reflows: bool,
pub shrink_priority: ShrinkPriority,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ShrinkPriority {
#[default]
Normal,
First,
}
impl Default for LayoutConstraints {
fn default() -> Self {
Self {
min_w: Length::Px(0),
min_h: Length::Px(0),
max_w: None,
max_h: None,
focus_min_w: 0,
focus_min_h: 0,
collapse_w: None,
collapse_h: None,
force_compact: false,
reflows: false,
shrink_priority: ShrinkPriority::Normal,
}
}
}
impl LayoutConstraints {
pub fn min_width(mut self, v: Length) -> Self {
self.min_w = v;
self
}
pub fn min_height(mut self, v: Length) -> Self {
self.min_h = v;
self
}
pub fn max_width(mut self, v: Length) -> Self {
self.max_w = Some(v);
self
}
pub fn max_height(mut self, v: Length) -> Self {
self.max_h = Some(v);
self
}
pub fn reflows(mut self, v: bool) -> Self {
self.reflows = v;
self
}
pub fn shrink_priority(mut self, v: ShrinkPriority) -> Self {
self.shrink_priority = v;
self
}
pub(crate) fn clamp_width(&self, natural: u16, available: u16) -> u16 {
let min = self.min_w.resolve_as_min(available);
let mut w = natural.max(min);
if let Some(max) = self.max_w.and_then(|l| l.resolve_as_max(available)) {
w = w.min(max).max(min);
}
w
}
pub(crate) fn clamp_height(&self, natural: u16, available: u16) -> u16 {
let min = self.min_h.resolve_as_min(available);
let mut h = natural.max(min);
if let Some(max) = self.max_h.and_then(|l| l.resolve_as_max(available)) {
h = h.min(max).max(min);
}
h
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Length {
#[default]
Auto,
Px(u16),
Percent(u16),
Flex(u16),
}
impl Length {
pub fn resolve(self, available: u16, content: u16) -> u16 {
match self {
Self::Auto => content,
Self::Px(px) => px,
Self::Percent(percent) => {
let percent = percent.min(100);
((available as u32).saturating_mul(percent as u32) / 100).min(u16::MAX as u32)
as u16
}
Self::Flex(_) => available,
}
}
pub(crate) fn resolve_as_min(self, available: u16) -> u16 {
match self {
Self::Px(px) => px,
Self::Percent(p) => {
if available == u16::MAX {
0
} else {
((available as u32 * p.min(100) as u32) / 100).min(u16::MAX as u32) as u16
}
}
Self::Auto | Self::Flex(_) => 0,
}
}
pub(crate) fn resolve_as_max(self, available: u16) -> Option<u16> {
match self {
Self::Px(px) => Some(px),
Self::Percent(p) => {
if available == u16::MAX {
None
} else {
Some(((available as u32 * p.min(100) as u32) / 100).min(u16::MAX as u32) as u16)
}
}
Self::Auto | Self::Flex(_) => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Size {
#[default]
Auto,
Fixed(u16),
Percent(u16),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Align {
#[default]
Start,
Center,
End,
Stretch,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Justify {
#[default]
Start,
Center,
End,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clamp_width_enforces_min_and_max() {
let c = LayoutConstraints::default()
.min_width(Length::Px(10))
.max_width(Length::Px(50));
assert_eq!(c.clamp_width(30, 100), 30);
assert_eq!(c.clamp_width(5, 100), 10);
assert_eq!(c.clamp_width(80, 100), 50);
let uncapped = LayoutConstraints::default().min_width(Length::Px(10));
assert_eq!(uncapped.clamp_width(u16::MAX, 1000), u16::MAX);
}
#[test]
fn clamp_height_min_wins_over_max() {
let c = LayoutConstraints::default()
.min_height(Length::Px(40))
.max_height(Length::Px(20));
assert_eq!(c.clamp_height(0, 100), 40);
assert_eq!(c.clamp_height(30, 100), 40);
assert_eq!(c.clamp_height(100, 100), 40);
}
#[test]
fn clamp_zero_constraints_and_min_eq_max() {
let zero = LayoutConstraints::default();
assert_eq!(zero.clamp_width(0, 100), 0);
assert_eq!(zero.clamp_height(0, 100), 0);
let pinned = LayoutConstraints::default()
.min_width(Length::Px(25))
.max_width(Length::Px(25));
assert_eq!(pinned.clamp_width(0, 100), 25);
assert_eq!(pinned.clamp_width(25, 100), 25);
assert_eq!(pinned.clamp_width(100, 100), 25);
}
#[test]
fn clamp_percent_constraints() {
let c = LayoutConstraints::default()
.min_width(Length::Percent(50))
.max_width(Length::Percent(80));
assert_eq!(c.clamp_width(0, 100), 50); assert_eq!(c.clamp_width(60, 100), 60); assert_eq!(c.clamp_width(90, 100), 80); assert_eq!(c.clamp_width(0, 200), 100); assert_eq!(c.clamp_width(200, 200), 160); }
#[test]
fn clamp_auto_flex_constraints_mean_no_constraint() {
let c = LayoutConstraints::default()
.min_width(Length::Auto)
.max_width(Length::Flex(1));
assert_eq!(c.clamp_width(0, 100), 0);
assert_eq!(c.clamp_width(9999, 100), 9999);
}
#[test]
fn length_resolve_variants() {
assert_eq!(Length::Auto.resolve(100, 42), 42);
assert_eq!(Length::Auto.resolve(0, 7), 7);
assert_eq!(Length::Px(60).resolve(100, 42), 60);
assert_eq!(Length::Px(0).resolve(100, 42), 0);
assert_eq!(Length::Percent(50).resolve(100, 42), 50);
assert_eq!(Length::Percent(33).resolve(300, 42), 99);
assert_eq!(Length::Percent(0).resolve(100, 42), 0);
assert_eq!(Length::Percent(150).resolve(80, 42), 80);
assert_eq!(Length::Flex(1).resolve(100, 42), 100);
assert_eq!(Length::Flex(3).resolve(200, 10), 200);
assert_eq!(Length::Flex(0).resolve(50, 50), 50);
}
#[test]
fn layout_constraints_builder_and_defaults() {
let defaults = LayoutConstraints::default();
assert_eq!(defaults.min_w, Length::Px(0));
assert_eq!(defaults.min_h, Length::Px(0));
assert_eq!(defaults.max_w, None);
assert_eq!(defaults.max_h, None);
assert!(!defaults.force_compact);
assert!(!defaults.reflows);
assert_eq!(defaults.shrink_priority, ShrinkPriority::Normal);
let c = LayoutConstraints::default()
.min_width(Length::Px(5))
.min_height(Length::Px(10))
.max_width(Length::Px(100))
.max_height(Length::Px(200))
.reflows(true)
.shrink_priority(ShrinkPriority::First);
assert_eq!(c.min_w, Length::Px(5));
assert_eq!(c.min_h, Length::Px(10));
assert_eq!(c.max_w, Some(Length::Px(100)));
assert_eq!(c.max_h, Some(Length::Px(200)));
assert!(c.reflows);
assert_eq!(c.shrink_priority, ShrinkPriority::First);
}
}