use super::pt::Pt;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct CalcExpr {
pub length: Pt,
pub percent: f64,
}
impl CalcExpr {
#[must_use]
pub const fn from_length(pt: Pt) -> Self {
Self {
length: pt,
percent: 0.0,
}
}
#[must_use]
pub const fn from_percent(p: f64) -> Self {
Self {
length: Pt::ZERO,
percent: p,
}
}
#[must_use]
pub fn resolve(&self, available: f64) -> f64 {
self.length.get() + self.percent * available
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Dimension {
#[default]
Auto,
Length(Pt),
Percent(f64),
Calc(CalcExpr),
Min(CalcExpr, CalcExpr),
Max(CalcExpr, CalcExpr),
Clamp {
min: CalcExpr,
val: CalcExpr,
max: CalcExpr,
},
}
impl Dimension {
#[must_use]
pub fn resolve(&self, available: f64) -> Option<f64> {
match self {
Self::Auto => None,
Self::Length(pt) => Some(pt.get()),
Self::Percent(p) => Some(p * available),
Self::Calc(e) => Some(e.resolve(available)),
Self::Min(a, b) => Some(a.resolve(available).min(b.resolve(available))),
Self::Max(a, b) => Some(a.resolve(available).max(b.resolve(available))),
Self::Clamp { min, val, max } => {
let v = val.resolve(available);
Some(v.clamp(min.resolve(available), max.resolve(available)))
}
}
}
#[must_use]
pub fn needs_resolution(&self) -> bool {
matches!(
self,
Self::Calc(_) | Self::Min(_, _) | Self::Max(_, _) | Self::Clamp { .. }
)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LengthPercentage {
Length(Pt),
Percent(f64),
Calc(CalcExpr),
Min(CalcExpr, CalcExpr),
Max(CalcExpr, CalcExpr),
Clamp {
min: CalcExpr,
val: CalcExpr,
max: CalcExpr,
},
}
impl Default for LengthPercentage {
fn default() -> Self {
Self::Length(Pt::ZERO)
}
}
impl LengthPercentage {
pub const ZERO: Self = Self::Length(Pt::ZERO);
#[must_use]
pub fn resolve(&self, available: f64) -> f64 {
match self {
Self::Length(pt) => pt.get(),
Self::Percent(p) => p * available,
Self::Calc(e) => e.resolve(available),
Self::Min(a, b) => a.resolve(available).min(b.resolve(available)),
Self::Max(a, b) => a.resolve(available).max(b.resolve(available)),
Self::Clamp { min, val, max } => val
.resolve(available)
.clamp(min.resolve(available), max.resolve(available)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calc_expr_resolve() {
let e = CalcExpr {
length: Pt::new(-20.0),
percent: 1.0,
};
assert!((e.resolve(500.0) - 480.0).abs() < 0.01);
}
#[test]
fn dimension_calc_resolve() {
let d = Dimension::Calc(CalcExpr {
length: Pt::new(10.0),
percent: 0.5,
});
assert!((d.resolve(200.0).unwrap() - 110.0).abs() < 0.01);
}
#[test]
fn dimension_min_resolve() {
let d = Dimension::Min(
CalcExpr::from_length(Pt::new(300.0)),
CalcExpr::from_percent(0.5),
);
assert!((d.resolve(500.0).unwrap() - 250.0).abs() < 0.01);
assert!((d.resolve(800.0).unwrap() - 300.0).abs() < 0.01);
}
#[test]
fn dimension_max_resolve() {
let d = Dimension::Max(
CalcExpr::from_length(Pt::new(100.0)),
CalcExpr::from_percent(0.5),
);
assert!((d.resolve(150.0).unwrap() - 100.0).abs() < 0.01);
assert!((d.resolve(400.0).unwrap() - 200.0).abs() < 0.01);
}
#[test]
fn dimension_clamp_resolve() {
let d = Dimension::Clamp {
min: CalcExpr::from_length(Pt::new(100.0)),
val: CalcExpr::from_percent(0.5),
max: CalcExpr::from_length(Pt::new(300.0)),
};
assert!((d.resolve(150.0).unwrap() - 100.0).abs() < 0.01);
assert!((d.resolve(400.0).unwrap() - 200.0).abs() < 0.01);
assert!((d.resolve(800.0).unwrap() - 300.0).abs() < 0.01);
}
#[test]
fn dimension_auto_returns_none() {
assert!(Dimension::Auto.resolve(500.0).is_none());
}
#[test]
fn length_percentage_calc() {
let lp = LengthPercentage::Calc(CalcExpr {
length: Pt::new(10.0),
percent: 0.25,
});
assert!((lp.resolve(200.0) - 60.0).abs() < 0.01);
}
}