use crate::{
unit::{sec, Ms, Sec},
Style, StyleUpdater,
};
use derive_rich::Rich;
use indexmap::IndexMap;
use std::{borrow::Cow, fmt};
#[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;