use std::ops::Sub;
use crate::{Dp, Px};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DimensionValue {
Fixed(Px),
Wrap { min: Option<Px>, max: Option<Px> },
Fill { min: Option<Px>, max: Option<Px> },
}
impl Default for DimensionValue {
fn default() -> Self {
Self::Wrap {
min: None,
max: None,
}
}
}
impl DimensionValue {
pub const ZERO: Self = Self::Fixed(Px(0));
pub const FILLED: Self = Self::Fill {
min: None,
max: None,
};
pub const WRAP: Self = Self::Wrap {
min: None,
max: None,
};
pub const fn get_max(&self) -> Option<Px> {
match self {
Self::Fixed(value) => Some(*value),
Self::Wrap { max, .. } => *max,
Self::Fill { max, .. } => *max,
}
}
pub fn get_min(&self) -> Option<Px> {
match self {
Self::Fixed(value) => Some(*value),
Self::Wrap { min, .. } => *min,
Self::Fill { min, .. } => *min,
}
}
}
impl From<Px> for DimensionValue {
fn from(value: Px) -> Self {
Self::Fixed(value)
}
}
impl From<Dp> for DimensionValue {
fn from(value: Dp) -> Self {
Self::Fixed(value.into())
}
}
impl Sub<Px> for DimensionValue {
type Output = DimensionValue;
fn sub(self, rhs: Px) -> Self::Output {
match self {
Self::Fixed(px) => Self::Fixed(px - rhs),
Self::Wrap { min, max } => Self::Wrap {
min,
max: max.map(|m| m - rhs),
},
Self::Fill { min, max } => Self::Fill {
min,
max: max.map(|m| m - rhs),
},
}
}
}
impl std::ops::Add<Px> for DimensionValue {
type Output = DimensionValue;
fn add(self, rhs: Px) -> Self::Output {
match self {
Self::Fixed(px) => Self::Fixed(px + rhs),
Self::Wrap { min, max } => Self::Wrap {
min,
max: max.map(|m| m + rhs),
},
Self::Fill { min, max } => Self::Fill {
min,
max: max.map(|m| m + rhs),
},
}
}
}
impl std::ops::AddAssign<Px> for DimensionValue {
fn add_assign(&mut self, rhs: Px) {
match self {
Self::Fixed(px) => *px = *px + rhs,
Self::Wrap { max, .. } => {
if let Some(m) = max {
*m = *m + rhs;
}
}
Self::Fill { max, .. } => {
if let Some(m) = max {
*m = *m + rhs;
}
}
}
}
}
impl std::ops::SubAssign<Px> for DimensionValue {
fn sub_assign(&mut self, rhs: Px) {
match self {
Self::Fixed(px) => *px = *px - rhs,
Self::Wrap { max, .. } => {
if let Some(m) = max {
*m = *m - rhs;
}
}
Self::Fill { max, .. } => {
if let Some(m) = max {
*m = *m - rhs;
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Constraint {
pub width: DimensionValue,
pub height: DimensionValue,
}
impl Constraint {
pub const NONE: Self = Self {
width: DimensionValue::Wrap {
min: None,
max: None,
},
height: DimensionValue::Wrap {
min: None,
max: None,
},
};
pub fn new(width: DimensionValue, height: DimensionValue) -> Self {
Self { width, height }
}
pub fn merge(&self, parent_constraint: &Constraint) -> Self {
let new_width = Self::merge_dimension(self.width, parent_constraint.width);
let new_height = Self::merge_dimension(self.height, parent_constraint.height);
Constraint::new(new_width, new_height)
}
fn merge_dimension(child_dim: DimensionValue, parent_dim: DimensionValue) -> DimensionValue {
match child_dim {
DimensionValue::Fixed(cv) => DimensionValue::Fixed(cv), DimensionValue::Wrap {
min: c_min,
max: c_max,
} => match parent_dim {
DimensionValue::Fixed(pv) => DimensionValue::Wrap {
min: c_min, max: match c_max {
Some(c) => Some(c.min(pv)), None => Some(pv), },
},
DimensionValue::Wrap {
min: _p_min,
max: p_max,
} => DimensionValue::Wrap {
min: c_min, max: match (c_max, p_max) {
(Some(c), Some(p)) => Some(c.min(p)), (Some(c), None) => Some(c),
(None, Some(p)) => Some(p),
(None, None) => None,
},
},
DimensionValue::Fill {
min: _p_fill_min,
max: p_fill_max,
} => DimensionValue::Wrap {
min: c_min, max: match (c_max, p_fill_max) {
(Some(c), Some(p)) => Some(c.min(p)), (Some(c), None) => Some(c),
(None, Some(p)) => Some(p),
(None, None) => None,
},
},
},
DimensionValue::Fill {
min: c_fill_min,
max: c_fill_max,
} => match parent_dim {
DimensionValue::Fixed(pv) => {
DimensionValue::Fill {
min: c_fill_min, max: match c_fill_max {
Some(c) => Some(c.min(pv)), None => Some(pv), },
}
}
DimensionValue::Wrap {
min: p_wrap_min,
max: p_wrap_max,
} => DimensionValue::Fill {
min: c_fill_min.or(p_wrap_min), max: match (c_fill_max, p_wrap_max) {
(Some(cf), Some(pw)) => Some(cf.min(pw)),
(Some(cf), None) => Some(cf),
(None, Some(pw)) => Some(pw),
(None, None) => None,
},
},
DimensionValue::Fill {
min: p_fill_min,
max: p_fill_max,
} => {
let new_min = match (c_fill_min, p_fill_min) {
(Some(cm), Some(pm)) => Some(cm.max(pm)),
(Some(cm), None) => Some(cm),
(None, Some(pm)) => Some(pm),
(None, None) => None,
};
let new_max = match (c_fill_max, p_fill_max) {
(Some(cm), Some(pm)) => Some(cm.min(pm)),
(Some(cm), None) => Some(cm),
(None, Some(pm)) => Some(pm),
(None, None) => None,
};
let (final_min, final_max) = match (new_min, new_max) {
(Some(n_min), Some(n_max)) if n_min > n_max => (Some(n_max), Some(n_max)), _ => (new_min, new_max),
};
DimensionValue::Fill {
min: final_min,
max: final_max,
}
}
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fixed_parent_wrap_child_wrap_grandchild() {
let parent = Constraint::new(
DimensionValue::Fixed(Px(100)),
DimensionValue::Fixed(Px(100)),
);
let child = Constraint::new(
DimensionValue::Wrap {
min: Some(Px(20)),
max: Some(Px(80)),
},
DimensionValue::Wrap {
min: Some(Px(20)),
max: Some(Px(80)),
},
);
let grandchild = Constraint::new(
DimensionValue::Wrap {
min: Some(Px(10)),
max: Some(Px(50)),
},
DimensionValue::Wrap {
min: Some(Px(10)),
max: Some(Px(50)),
},
);
let merged_child = child.merge(&parent);
assert_eq!(
merged_child.width,
DimensionValue::Wrap {
min: Some(Px(20)),
max: Some(Px(80))
}
);
assert_eq!(
merged_child.height,
DimensionValue::Wrap {
min: Some(Px(20)),
max: Some(Px(80))
}
);
let final_result = grandchild.merge(&merged_child);
assert_eq!(
final_result.width,
DimensionValue::Wrap {
min: Some(Px(10)),
max: Some(Px(50))
}
);
assert_eq!(
final_result.height,
DimensionValue::Wrap {
min: Some(Px(10)),
max: Some(Px(50))
}
);
}
#[test]
fn test_fill_parent_wrap_child() {
let parent = Constraint::new(
DimensionValue::Fill {
min: Some(Px(50)),
max: Some(Px(200)),
},
DimensionValue::Fill {
min: Some(Px(50)),
max: Some(Px(200)),
},
);
let child = Constraint::new(
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(150)),
},
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(150)),
},
);
let result = child.merge(&parent);
assert_eq!(
result.width,
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(150))
}
);
assert_eq!(
result.height,
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(150))
}
);
}
#[test]
fn test_fill_parent_wrap_child_no_child_min() {
let parent = Constraint::new(
DimensionValue::Fill {
min: Some(Px(50)),
max: Some(Px(200)),
},
DimensionValue::Fill {
min: Some(Px(50)),
max: Some(Px(200)),
},
);
let child = Constraint::new(
DimensionValue::Wrap {
min: None,
max: Some(Px(150)),
},
DimensionValue::Wrap {
min: None,
max: Some(Px(150)),
},
);
let result = child.merge(&parent);
assert_eq!(
result.width,
DimensionValue::Wrap {
min: None,
max: Some(Px(150))
}
);
assert_eq!(
result.height,
DimensionValue::Wrap {
min: None,
max: Some(Px(150))
}
);
}
#[test]
fn test_fill_parent_wrap_child_no_parent_max() {
let parent = Constraint::new(
DimensionValue::Fill {
min: Some(Px(50)),
max: None,
},
DimensionValue::Fill {
min: Some(Px(50)),
max: None,
},
);
let child = Constraint::new(
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(150)),
},
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(150)),
},
);
let result = child.merge(&parent);
assert_eq!(
result.width,
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(150))
}
);
assert_eq!(
result.height,
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(150))
}
);
}
#[test]
fn test_fixed_parent_wrap_child() {
let parent = Constraint::new(
DimensionValue::Fixed(Px(100)),
DimensionValue::Fixed(Px(100)),
);
let child = Constraint::new(
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(120)),
},
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(120)),
},
);
let result = child.merge(&parent);
assert_eq!(
result.width,
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(100))
}
);
assert_eq!(
result.height,
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(100))
}
);
}
#[test]
fn test_fixed_parent_wrap_child_no_child_max() {
let parent = Constraint::new(
DimensionValue::Fixed(Px(100)),
DimensionValue::Fixed(Px(100)),
);
let child = Constraint::new(
DimensionValue::Wrap {
min: Some(Px(30)),
max: None,
},
DimensionValue::Wrap {
min: Some(Px(30)),
max: None,
},
);
let result = child.merge(&parent);
assert_eq!(
result.width,
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(100))
}
);
assert_eq!(
result.height,
DimensionValue::Wrap {
min: Some(Px(30)),
max: Some(Px(100))
}
);
}
#[test]
fn test_fixed_parent_fill_child() {
let parent = Constraint::new(
DimensionValue::Fixed(Px(100)),
DimensionValue::Fixed(Px(100)),
);
let child = Constraint::new(
DimensionValue::Fill {
min: Some(Px(30)),
max: Some(Px(120)),
},
DimensionValue::Fill {
min: Some(Px(30)),
max: Some(Px(120)),
},
);
let result = child.merge(&parent);
assert_eq!(
result.width,
DimensionValue::Fill {
min: Some(Px(30)),
max: Some(Px(100))
}
);
assert_eq!(
result.height,
DimensionValue::Fill {
min: Some(Px(30)),
max: Some(Px(100))
}
);
}
#[test]
fn test_fixed_parent_fill_child_no_child_max() {
let parent = Constraint::new(
DimensionValue::Fixed(Px(100)),
DimensionValue::Fixed(Px(100)),
);
let child = Constraint::new(
DimensionValue::Fill {
min: Some(Px(30)),
max: None,
},
DimensionValue::Fill {
min: Some(Px(30)),
max: None,
},
);
let result = child.merge(&parent);
assert_eq!(
result.width,
DimensionValue::Fill {
min: Some(Px(30)),
max: Some(Px(100))
}
);
assert_eq!(
result.height,
DimensionValue::Fill {
min: Some(Px(30)),
max: Some(Px(100))
}
);
}
#[test]
fn test_fixed_parent_fill_child_no_child_min() {
let parent = Constraint::new(
DimensionValue::Fixed(Px(100)),
DimensionValue::Fixed(Px(100)),
);
let child = Constraint::new(
DimensionValue::Fill {
min: None,
max: Some(Px(120)),
},
DimensionValue::Fill {
min: None,
max: Some(Px(120)),
},
);
let result = child.merge(&parent);
assert_eq!(
result.width,
DimensionValue::Fill {
min: None,
max: Some(Px(100))
}
);
assert_eq!(
result.height,
DimensionValue::Fill {
min: None,
max: Some(Px(100))
}
);
}
}