Skip to main content

whisker_css/keyword/
animation.rs

1//! Animation and transition keyword enums.
2//!
3//! References:
4//! - <https://lynxjs.org/api/css/properties/animation-direction>
5//! - <https://lynxjs.org/api/css/properties/animation-fill-mode>
6//! - <https://lynxjs.org/api/css/properties/animation-iteration-count>
7//! - <https://lynxjs.org/api/css/properties/animation-play-state>
8//! - <https://lynxjs.org/api/css/properties/transition-property>
9
10use core::fmt;
11
12use crate::data_type::CssString;
13use crate::to_css::{write_number, ToCss};
14
15/// The `animation-direction` keyword.
16#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
17pub enum AnimationDirection {
18    /// `normal` — play forward each iteration. Default.
19    Normal,
20    /// `reverse` — play backward each iteration.
21    Reverse,
22    /// `alternate` — alternate forward, backward.
23    Alternate,
24    /// `alternate-reverse` — alternate backward, forward.
25    AlternateReverse,
26}
27
28impl ToCss for AnimationDirection {
29    fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
30        dest.write_str(match self {
31            AnimationDirection::Normal => "normal",
32            AnimationDirection::Reverse => "reverse",
33            AnimationDirection::Alternate => "alternate",
34            AnimationDirection::AlternateReverse => "alternate-reverse",
35        })
36    }
37}
38
39/// The `animation-fill-mode` keyword. Controls how the animated
40/// values apply before/after the active period.
41#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
42pub enum AnimationFillMode {
43    /// `none` — no fill outside the active period. Default.
44    None,
45    /// `forwards` — keep the final keyframe values after the
46    /// animation completes.
47    Forwards,
48    /// `backwards` — apply the initial keyframe values during
49    /// `animation-delay`.
50    Backwards,
51    /// `both` — `forwards` and `backwards` combined.
52    Both,
53}
54
55impl ToCss for AnimationFillMode {
56    fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
57        dest.write_str(match self {
58            AnimationFillMode::None => "none",
59            AnimationFillMode::Forwards => "forwards",
60            AnimationFillMode::Backwards => "backwards",
61            AnimationFillMode::Both => "both",
62        })
63    }
64}
65
66/// The `animation-iteration-count` value: either `infinite` or a
67/// non-negative number of iterations.
68#[derive(Copy, Clone, Debug, PartialEq)]
69pub enum AnimationIterationCount {
70    /// `infinite` — repeat forever.
71    Infinite,
72    /// Explicit iteration count.
73    Count(f32),
74}
75
76impl ToCss for AnimationIterationCount {
77    fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
78        match self {
79            AnimationIterationCount::Infinite => dest.write_str("infinite"),
80            AnimationIterationCount::Count(n) => write_number(dest, *n),
81        }
82    }
83}
84
85/// The `animation-play-state` keyword.
86#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
87pub enum AnimationPlayState {
88    /// `running` — animation is playing. Default.
89    Running,
90    /// `paused` — animation is paused.
91    Paused,
92}
93
94impl ToCss for AnimationPlayState {
95    fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
96        dest.write_str(match self {
97            AnimationPlayState::Running => "running",
98            AnimationPlayState::Paused => "paused",
99        })
100    }
101}
102
103/// The `transition-property` value: either the `all`/`none` keyword
104/// or one specific property name.
105#[derive(Clone, Debug, PartialEq, Eq, Hash)]
106pub enum TransitionPropertyKind {
107    /// `all` — transition every animatable property.
108    All,
109    /// `none` — disable transitions on this element.
110    None,
111    /// A specific CSS property name (`opacity`, `transform`, …).
112    Name(CssString),
113}
114
115impl TransitionPropertyKind {
116    /// Build [`TransitionPropertyKind::Name`] from anything string-like.
117    pub fn name(s: impl Into<String>) -> Self {
118        Self::Name(CssString::new(s))
119    }
120}
121
122impl ToCss for TransitionPropertyKind {
123    fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
124        match self {
125            TransitionPropertyKind::All => dest.write_str("all"),
126            TransitionPropertyKind::None => dest.write_str("none"),
127            // Property names are CSS identifiers, written bare (no
128            // surrounding quotes).
129            TransitionPropertyKind::Name(n) => dest.write_str(n.as_str()),
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn animation_direction_all() {
140        let cases = [
141            (AnimationDirection::Normal, "normal"),
142            (AnimationDirection::Reverse, "reverse"),
143            (AnimationDirection::Alternate, "alternate"),
144            (AnimationDirection::AlternateReverse, "alternate-reverse"),
145        ];
146        for (k, expected) in cases {
147            assert_eq!(k.to_css_string(), expected);
148        }
149    }
150
151    #[test]
152    fn animation_fill_mode_all() {
153        let cases = [
154            (AnimationFillMode::None, "none"),
155            (AnimationFillMode::Forwards, "forwards"),
156            (AnimationFillMode::Backwards, "backwards"),
157            (AnimationFillMode::Both, "both"),
158        ];
159        for (k, expected) in cases {
160            assert_eq!(k.to_css_string(), expected);
161        }
162    }
163
164    #[test]
165    fn iteration_count_keyword() {
166        assert_eq!(
167            AnimationIterationCount::Infinite.to_css_string(),
168            "infinite"
169        );
170    }
171
172    #[test]
173    fn iteration_count_number() {
174        assert_eq!(AnimationIterationCount::Count(2.0).to_css_string(), "2");
175        assert_eq!(AnimationIterationCount::Count(0.5).to_css_string(), "0.5");
176    }
177
178    #[test]
179    fn play_state_all() {
180        assert_eq!(AnimationPlayState::Running.to_css_string(), "running");
181        assert_eq!(AnimationPlayState::Paused.to_css_string(), "paused");
182    }
183
184    #[test]
185    fn transition_property_all() {
186        assert_eq!(TransitionPropertyKind::All.to_css_string(), "all");
187        assert_eq!(TransitionPropertyKind::None.to_css_string(), "none");
188        assert_eq!(
189            TransitionPropertyKind::name("opacity").to_css_string(),
190            "opacity"
191        );
192    }
193}