css-style 0.14.1

Typed CSS Style
Documentation
use crate::{
    unit::{sec, Ms, Sec},
    Style, StyleUpdater,
};
use derive_rich::Rich;
use indexmap::IndexMap;
use std::{borrow::Cow, fmt};

/// ```
/// use css_style::{prelude::*, unit::{sec, ms}};
///
/// style()
///     // transition for all properties
///     .and_transition(|conf| conf.duration(sec(1.2)).ease())
///     // transitions for specific properties
///     .and_transition(|conf| {
///         conf.insert("opacity", |conf| conf.duration(ms(150.)).ease().delay(sec(0.5)))
///             .insert("width", |conf| conf.duration(ms(450.)).ease_in())
///     });
/// ```
#[derive(Clone, Debug, PartialEq, From, Display)]
pub enum Transition {
    #[from(forward)]
    One(TransitionValue),
    #[display(
        fmt = "{}",
        "_0.iter().map(|(prop, trans)| format!(\"{} {}\", prop, trans)).collect::<Vec<_>>().join(\", \")"
    )]
    Multiple(IndexMap<Cow<'static, str>, TransitionValue>),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
    #[display(fmt = "none")]
    None,
    #[display(fmt = "unset")]
    Unset,
}

impl Default for Transition {
    fn default() -> Self {
        Transition::Multiple(IndexMap::default())
    }
}

impl StyleUpdater for Transition {
    fn update_style(self, style: Style) -> Style {
        style.insert("transition", self)
    }
}

impl Transition {
    fn transition(mut self, conf: impl FnOnce(TransitionValue) -> TransitionValue) -> Self {
        self = match self {
            Self::One(val) => Self::One(conf(val)),
            _ => Self::One(conf(TransitionValue::default())),
        };
        self
    }

    pub fn new() -> Self {
        Self::default()
    }

    pub fn duration(self, val: impl Into<Duration>) -> Self {
        self.transition(|t| t.duration(val))
    }

    pub fn timing_function(self, val: impl Into<TimingFunction>) -> Self {
        self.transition(|t| t.timing_function(val))
    }

    pub fn try_timing_function(self, val: Option<impl Into<TimingFunction>>) -> Self {
        self.transition(|t| t.try_timing_function(val))
    }

    pub fn ease(self) -> Self {
        self.transition(|t| t.ease())
    }

    pub fn linear(self) -> Self {
        self.transition(|t| t.linear())
    }

    pub fn ease_in(self) -> Self {
        self.transition(|t| t.ease_in())
    }

    pub fn ease_out(self) -> Self {
        self.transition(|t| t.ease_out())
    }

    pub fn ease_in_out(self) -> Self {
        self.transition(|t| t.ease_in_out())
    }

    pub fn step_start(self) -> Self {
        self.transition(|t| t.step_start())
    }

    pub fn step_end(self) -> Self {
        self.transition(|t| t.step_end())
    }

    pub fn steps(self, intervals: usize, pos: impl Into<StepsPos>) -> Self {
        self.transition(|t| t.steps(intervals, pos))
    }

    pub fn cubic_bezier(self, n1: f32, n2: f32, n3: f32, n4: f32) -> Self {
        self.transition(|t| t.cubic_bezier(n1, n2, n3, n4))
    }

    pub fn delay(self, val: impl Into<Delay>) -> Self {
        self.transition(|t| t.delay(val))
    }

    pub fn try_delay(self, val: Option<impl Into<Delay>>) -> Self {
        self.transition(|t| t.try_delay(val))
    }

    #[allow(clippy::should_implement_trait)]
    pub fn insert(
        mut self,
        property: impl Into<Cow<'static, str>>,
        get_val: impl FnOnce(TransitionValue) -> TransitionValue,
    ) -> Self {
        let val = get_val(TransitionValue::default());
        self = match self {
            Self::Multiple(mut map) => {
                map.insert(property.into(), val);
                Self::Multiple(map)
            }
            _ => {
                let mut map = IndexMap::default();
                map.insert(property.into(), val);
                Self::Multiple(map)
            }
        };
        self
    }

    pub fn foreach(
        mut self,
        properties: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
        f: impl FnOnce(TransitionValue) -> TransitionValue + Clone,
    ) -> Self {
        for prop in properties.into_iter() {
            self = self.insert(prop.into(), f.clone());
        }
        self
    }
}

#[derive(Rich, Clone, Debug, PartialEq, From)]
pub struct TransitionValue {
    #[rich(write)]
    pub duration: Duration,
    #[rich(write(rename = timing_function), write(option, rename = try_timing_function), value_fns = {
        ease = TimingFunction::Ease,
        linear = TimingFunction::Linear,
        ease_in = TimingFunction::EaseIn,
        ease_out = TimingFunction::EaseOut,
        ease_in_out = TimingFunction::EaseInOut,
        step_start = TimingFunction::StepStart,
        step_end = TimingFunction::StepEnd,
    })]
    pub timing_function: Option<TimingFunction>,
    #[rich(write, write(option))]
    pub delay: Option<Delay>,
}

impl fmt::Display for TransitionValue {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.duration)?;

        if let Some(timing_fn) = self.timing_function {
            write!(f, " {}", timing_fn)?;
        }

        if let Some(delay) = self.delay {
            write!(f, " {}", delay)?;
        }

        Ok(())
    }
}

impl Default for TransitionValue {
    fn default() -> Self {
        Self::new(Duration::Unset)
    }
}

impl TransitionValue {
    pub fn new(duration: impl Into<Duration>) -> Self {
        Self {
            duration: duration.into(),
            timing_function: None,
            delay: None,
        }
    }

    pub fn steps(mut self, intervals: usize, pos: impl Into<StepsPos>) -> Self {
        self.timing_function = Some(TimingFunction::Steps(intervals, pos.into()));
        self
    }

    pub fn cubic_bezier(mut self, n1: f32, n2: f32, n3: f32, n4: f32) -> Self {
        self.timing_function = Some(TimingFunction::CubicBezier(n1, n2, n3, n4));
        self
    }
}

impl<T> From<T> for TransitionValue
where
    T: Into<Duration>,
{
    fn from(source: T) -> Self {
        TransitionValue::new(source)
    }
}

#[derive(Clone, Debug, Copy, PartialEq, Display, From)]
pub enum TimingFunction {
    #[display(fmt = "ease")]
    Ease,
    #[display(fmt = "linear")]
    Linear,
    #[display(fmt = "ease-in")]
    EaseIn,
    #[display(fmt = "ease-out")]
    EaseOut,
    #[display(fmt = "ease-in-out")]
    EaseInOut,
    #[display(fmt = "step-start")]
    StepStart,
    #[display(fmt = "step-end")]
    StepEnd,
    #[display(fmt = "steps({}, {})", _0, _1)]
    Steps(usize, StepsPos),
    #[display(fmt = "cubic-bezier({}, {}, {}, {})", _0, _1, _2, _3)]
    CubicBezier(f32, f32, f32, f32),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Debug, Copy, PartialEq, Display, From)]
pub enum StepsPos {
    #[display(fmt = "start")]
    Start,
    #[display(fmt = "end")]
    End,
}

#[derive(Clone, Debug, Copy, PartialEq, Display, From)]
pub enum Duration {
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
    #[display(fmt = "unset")]
    Unset,
    Ms(Ms),
    Sec(Sec),
}

impl From<std::time::Duration> for Duration {
    fn from(source: std::time::Duration) -> Self {
        sec(source.as_secs_f32()).into()
    }
}

pub type Delay = Duration;