deft 0.15.0

Cross platform ui framework
use crate::style::PropValueParse;
use ordered_float::OrderedFloat;
use std::str::FromStr;
use yoga::StyleUnit;

#[derive(Clone, Debug, PartialEq, Default)]
pub struct LengthContext {
    pub root: f32,
    pub font_size: f32,
    pub viewport_width: f32,
    pub viewport_height: f32,
}

#[derive(Clone, Debug, PartialEq, Copy)]
pub enum Length {
    PX(f32),
    CM(f32),
    MM(f32),
    IN(f32),
    PT(f32),

    EM(f32),
    REM(f32),
    VH(f32),
    VW(f32),
}

#[derive(Clone, Debug, PartialEq, Copy)]
pub enum LengthOrPercent {
    Length(Length),
    Percent(f32),
    Undefined,
    Auto,
}

impl LengthOrPercent {
    pub fn to_style_unit(&self, ctx: &LengthContext) -> StyleUnit {
        match self {
            LengthOrPercent::Length(v) => {
                let value = v.to_px(ctx);
                StyleUnit::Point(OrderedFloat(value))
            }
            LengthOrPercent::Percent(p) => StyleUnit::Percent(OrderedFloat(*p)),
            LengthOrPercent::Undefined => StyleUnit::UndefinedValue,
            LengthOrPercent::Auto => StyleUnit::Auto,
        }
    }
}

impl Length {
    pub fn from_str(value: &str) -> Option<Self> {
        let value = value.trim();
        let result = if let Some(v) = value.strip_suffix("px") {
            Self::PX(Self::parse_f32(v)?)
        } else if let Some(v) = value.strip_suffix("cm") {
            Self::CM(Self::parse_f32(v)?)
        } else if let Some(v) = value.strip_suffix("mm") {
            Self::MM(Self::parse_f32(v)?)
        } else if let Some(v) = value.strip_suffix("in") {
            Self::IN(Self::parse_f32(v)?)
        } else if let Some(v) = value.strip_suffix("pt") {
            Self::PT(Self::parse_f32(v)?)
        } else if let Some(v) = value.strip_suffix("em") {
            Self::EM(Self::parse_f32(v)?)
        } else if let Some(v) = value.strip_suffix("rem") {
            Self::REM(Self::parse_f32(v)?)
        } else if let Some(v) = value.strip_suffix("vh") {
            Self::VH(Self::parse_f32(v)?)
        } else if let Some(v) = value.strip_suffix("vw") {
            Self::VW(Self::parse_f32(v)?)
        } else {
            let v = Self::parse_f32(value)?;
            return Some(Self::PX(v));
        };
        Some(result)
    }

    pub fn update_value(&mut self, value: f32) {
        match self {
            Length::PX(x) => *x = value,
            Length::CM(x) => *x = value,
            Length::MM(x) => *x = value,
            Length::IN(x) => *x = value,
            Length::PT(x) => *x = value,
            Length::EM(x) => *x = value,
            Length::REM(x) => *x = value,
            Length::VH(x) => *x = value,
            Length::VW(x) => *x = value,
        }
    }

    pub fn to_px(&self, ctx: &LengthContext) -> f32 {
        match self {
            Length::EM(em) => em * ctx.font_size,
            Length::REM(rem) => rem * ctx.root,
            Length::VH(vh) => vh / 100.0 * ctx.viewport_height,
            Length::VW(vw) => vw / 100.0 * ctx.viewport_width,
            Length::PX(px) => *px,
            Length::CM(cm) => cm * 96.0 / 2.54,
            Length::MM(mm) => mm * 96.0 / 2.54 / 10.0,
            Length::IN(i) => i * 96.0,
            Length::PT(pt) => pt * 96.0 / 72.0,
        }
    }

    pub fn to_str(&self) -> String {
        match self {
            Length::PX(v) => format!("{}px", v),
            Length::CM(v) => format!("{}cm", v),
            Length::MM(v) => format!("{}mm", v),
            Length::IN(v) => format!("{}in", v),
            Length::PT(v) => format!("{}pt", v),
            Length::EM(v) => format!("{}em", v),
            Length::REM(v) => format!("{}rem", v),
            Length::VH(v) => format!("{}vh", v),
            Length::VW(v) => format!("{}vw", v),
        }
    }

    fn parse_f32(value: &str) -> Option<f32> {
        let value = value.trim();
        f32::from_str(value).ok()
    }
}

impl PropValueParse for LengthOrPercent {
    fn parse_prop_value(value: &str) -> Option<Self> {
        let value = value.trim();
        if value.eq_ignore_ascii_case("auto") {
            return Some(LengthOrPercent::Auto);
        } else if let Some(v) = Length::from_str(value) {
            return Some(Self::Length(v));
        } else {
            let value = value.trim();
            if let Some(v) = parse_percent(value) {
                return Some(Self::Percent(v));
            }
        }
        Some(LengthOrPercent::Undefined)
    }

    fn to_style_string(&self) -> String {
        match self {
            LengthOrPercent::Length(v) => v.to_style_string(),
            LengthOrPercent::Percent(v) => format!("{}%", v),
            LengthOrPercent::Undefined => "".to_string(),
            LengthOrPercent::Auto => "auto".to_string(),
        }
    }
}

impl PropValueParse for Length {
    fn parse_prop_value(value: &str) -> Option<Self> {
        Self::from_str(value)
    }

    fn to_style_string(&self) -> String {
        self.to_str()
    }
}

pub fn parse_percent(value: &str) -> Option<f32> {
    if let Some(v) = value.strip_suffix("%") {
        let v = f32::from_str(v.trim()).ok()?;
        Some(v)
    } else {
        None
    }
}