css_style/
transition.rs

1use crate::{
2    unit::{sec, Ms, Sec},
3    Style, StyleUpdater,
4};
5use derive_rich::Rich;
6use indexmap::IndexMap;
7use std::{borrow::Cow, fmt};
8
9/// ```
10/// use css_style::{prelude::*, unit::{sec, ms}};
11///
12/// style()
13///     // transition for all properties
14///     .and_transition(|conf| conf.duration(sec(1.2)).ease())
15///     // transitions for specific properties
16///     .and_transition(|conf| {
17///         conf.insert("opacity", |conf| conf.duration(ms(150.)).ease().delay(sec(0.5)))
18///             .insert("width", |conf| conf.duration(ms(450.)).ease_in())
19///     });
20/// ```
21#[derive(Clone, Debug, PartialEq, From, Display)]
22pub enum Transition {
23    #[from(forward)]
24    One(TransitionValue),
25    #[display(
26        fmt = "{}",
27        "_0.iter().map(|(prop, trans)| format!(\"{} {}\", prop, trans)).collect::<Vec<_>>().join(\", \")"
28    )]
29    Multiple(IndexMap<Cow<'static, str>, TransitionValue>),
30    #[display(fmt = "initial")]
31    Initial,
32    #[display(fmt = "inherit")]
33    Inherit,
34    #[display(fmt = "none")]
35    None,
36    #[display(fmt = "unset")]
37    Unset,
38}
39
40impl Default for Transition {
41    fn default() -> Self {
42        Transition::Multiple(IndexMap::default())
43    }
44}
45
46impl StyleUpdater for Transition {
47    fn update_style(self, style: Style) -> Style {
48        style.insert("transition", self)
49    }
50}
51
52impl Transition {
53    fn transition(mut self, conf: impl FnOnce(TransitionValue) -> TransitionValue) -> Self {
54        self = match self {
55            Self::One(val) => Self::One(conf(val)),
56            _ => Self::One(conf(TransitionValue::default())),
57        };
58        self
59    }
60
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    pub fn duration(self, val: impl Into<Duration>) -> Self {
66        self.transition(|t| t.duration(val))
67    }
68
69    pub fn timing_function(self, val: impl Into<TimingFunction>) -> Self {
70        self.transition(|t| t.timing_function(val))
71    }
72
73    pub fn try_timing_function(self, val: Option<impl Into<TimingFunction>>) -> Self {
74        self.transition(|t| t.try_timing_function(val))
75    }
76
77    pub fn ease(self) -> Self {
78        self.transition(|t| t.ease())
79    }
80
81    pub fn linear(self) -> Self {
82        self.transition(|t| t.linear())
83    }
84
85    pub fn ease_in(self) -> Self {
86        self.transition(|t| t.ease_in())
87    }
88
89    pub fn ease_out(self) -> Self {
90        self.transition(|t| t.ease_out())
91    }
92
93    pub fn ease_in_out(self) -> Self {
94        self.transition(|t| t.ease_in_out())
95    }
96
97    pub fn step_start(self) -> Self {
98        self.transition(|t| t.step_start())
99    }
100
101    pub fn step_end(self) -> Self {
102        self.transition(|t| t.step_end())
103    }
104
105    pub fn steps(self, intervals: usize, pos: impl Into<StepsPos>) -> Self {
106        self.transition(|t| t.steps(intervals, pos))
107    }
108
109    pub fn cubic_bezier(self, n1: f32, n2: f32, n3: f32, n4: f32) -> Self {
110        self.transition(|t| t.cubic_bezier(n1, n2, n3, n4))
111    }
112
113    pub fn delay(self, val: impl Into<Delay>) -> Self {
114        self.transition(|t| t.delay(val))
115    }
116
117    pub fn try_delay(self, val: Option<impl Into<Delay>>) -> Self {
118        self.transition(|t| t.try_delay(val))
119    }
120
121    #[allow(clippy::should_implement_trait)]
122    pub fn insert(
123        mut self,
124        property: impl Into<Cow<'static, str>>,
125        get_val: impl FnOnce(TransitionValue) -> TransitionValue,
126    ) -> Self {
127        let val = get_val(TransitionValue::default());
128        self = match self {
129            Self::Multiple(mut map) => {
130                map.insert(property.into(), val);
131                Self::Multiple(map)
132            }
133            _ => {
134                let mut map = IndexMap::default();
135                map.insert(property.into(), val);
136                Self::Multiple(map)
137            }
138        };
139        self
140    }
141
142    pub fn foreach(
143        mut self,
144        properties: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
145        f: impl FnOnce(TransitionValue) -> TransitionValue + Clone,
146    ) -> Self {
147        for prop in properties.into_iter() {
148            self = self.insert(prop.into(), f.clone());
149        }
150        self
151    }
152}
153
154#[derive(Rich, Clone, Debug, PartialEq, From)]
155pub struct TransitionValue {
156    #[rich(write)]
157    pub duration: Duration,
158    #[rich(write(rename = timing_function), write(option, rename = try_timing_function), value_fns = {
159        ease = TimingFunction::Ease,
160        linear = TimingFunction::Linear,
161        ease_in = TimingFunction::EaseIn,
162        ease_out = TimingFunction::EaseOut,
163        ease_in_out = TimingFunction::EaseInOut,
164        step_start = TimingFunction::StepStart,
165        step_end = TimingFunction::StepEnd,
166    })]
167    pub timing_function: Option<TimingFunction>,
168    #[rich(write, write(option))]
169    pub delay: Option<Delay>,
170}
171
172impl fmt::Display for TransitionValue {
173    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174        write!(f, "{}", self.duration)?;
175
176        if let Some(timing_fn) = self.timing_function {
177            write!(f, " {}", timing_fn)?;
178        }
179
180        if let Some(delay) = self.delay {
181            write!(f, " {}", delay)?;
182        }
183
184        Ok(())
185    }
186}
187
188impl Default for TransitionValue {
189    fn default() -> Self {
190        Self::new(Duration::Unset)
191    }
192}
193
194impl TransitionValue {
195    pub fn new(duration: impl Into<Duration>) -> Self {
196        Self {
197            duration: duration.into(),
198            timing_function: None,
199            delay: None,
200        }
201    }
202
203    pub fn steps(mut self, intervals: usize, pos: impl Into<StepsPos>) -> Self {
204        self.timing_function = Some(TimingFunction::Steps(intervals, pos.into()));
205        self
206    }
207
208    pub fn cubic_bezier(mut self, n1: f32, n2: f32, n3: f32, n4: f32) -> Self {
209        self.timing_function = Some(TimingFunction::CubicBezier(n1, n2, n3, n4));
210        self
211    }
212}
213
214impl<T> From<T> for TransitionValue
215where
216    T: Into<Duration>,
217{
218    fn from(source: T) -> Self {
219        TransitionValue::new(source)
220    }
221}
222
223#[derive(Clone, Debug, Copy, PartialEq, Display, From)]
224pub enum TimingFunction {
225    #[display(fmt = "ease")]
226    Ease,
227    #[display(fmt = "linear")]
228    Linear,
229    #[display(fmt = "ease-in")]
230    EaseIn,
231    #[display(fmt = "ease-out")]
232    EaseOut,
233    #[display(fmt = "ease-in-out")]
234    EaseInOut,
235    #[display(fmt = "step-start")]
236    StepStart,
237    #[display(fmt = "step-end")]
238    StepEnd,
239    #[display(fmt = "steps({}, {})", _0, _1)]
240    Steps(usize, StepsPos),
241    #[display(fmt = "cubic-bezier({}, {}, {}, {})", _0, _1, _2, _3)]
242    CubicBezier(f32, f32, f32, f32),
243    #[display(fmt = "initial")]
244    Initial,
245    #[display(fmt = "inherit")]
246    Inherit,
247}
248
249#[derive(Clone, Debug, Copy, PartialEq, Display, From)]
250pub enum StepsPos {
251    #[display(fmt = "start")]
252    Start,
253    #[display(fmt = "end")]
254    End,
255}
256
257#[derive(Clone, Debug, Copy, PartialEq, Display, From)]
258pub enum Duration {
259    #[display(fmt = "initial")]
260    Initial,
261    #[display(fmt = "inherit")]
262    Inherit,
263    #[display(fmt = "unset")]
264    Unset,
265    Ms(Ms),
266    Sec(Sec),
267}
268
269impl From<std::time::Duration> for Duration {
270    fn from(source: std::time::Duration) -> Self {
271        sec(source.as_secs_f32()).into()
272    }
273}
274
275pub type Delay = Duration;