1use super::prelude::*;
2use crate::{
3 AutoOr, CalcableValue, EasingFunction, KeyframesName, NonNegative, NoneOr, SingleAnimationDirection,
4 SingleAnimationFillMode, SingleAnimationIterationCount, SingleAnimationPlayState, SingleAnimationTimeline, Time,
5};
6use css_parse::parse_optionals;
7
8#[node]
23#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
25#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
26#[derive(csskit_derives::NodeWithMetadata)]
27pub struct SingleAnimation<'a> {
28 pub duration: Option<AutoOr<CalcableValue<'a, NonNegative<Time>>>>,
29 pub easing: Option<EasingFunction<'a>>,
30 pub delay: Option<CalcableValue<'a, Time>>,
31 pub iteration_count: Option<SingleAnimationIterationCount<'a>>,
32 pub direction: Option<SingleAnimationDirection>,
33 pub fill_mode: Option<SingleAnimationFillMode>,
34 pub play_state: Option<SingleAnimationPlayState>,
35 pub name: Option<NoneOr<KeyframesName>>,
36 pub timeline: Option<SingleAnimationTimeline>,
37}
38
39impl<'a> Peek<'a> for SingleAnimation<'a> {
40 const PEEK_KINDSET: KindSet = <AutoOr<CalcableValue<'a, NonNegative<Time>>>>::PEEK_KINDSET
41 .combine(EasingFunction::PEEK_KINDSET)
42 .combine(<CalcableValue<'a, Time>>::PEEK_KINDSET)
43 .combine(<SingleAnimationIterationCount<'a>>::PEEK_KINDSET)
44 .combine(<NoneOr<KeyframesName>>::PEEK_KINDSET);
45
46 #[inline(always)]
47 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
48 where
49 I: Iterator<Item = Cursor> + Clone,
50 {
51 <AutoOr<CalcableValue<'a, NonNegative<Time>>>>::peek(p, c)
52 || EasingFunction::peek(p, c)
53 || <CalcableValue<'a, Time>>::peek(p, c)
54 || <SingleAnimationIterationCount<'a>>::peek(p, c)
55 || <NoneOr<KeyframesName>>::peek(p, c)
56 }
57}
58
59impl<'a> Parse<'a> for SingleAnimation<'a> {
60 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
61 where
62 I: Iterator<Item = Cursor> + Clone,
63 {
64 let (duration, easing, delay, iteration_count, direction, fill_mode, play_state, name, timeline) = parse_optionals!(
65 p,
66 duration: AutoOr<CalcableValue<NonNegative<Time>>>,
67 easing: EasingFunction,
68 delay: CalcableValue<Time>,
69 iteration_count: SingleAnimationIterationCount,
70 direction: SingleAnimationDirection,
71 fill_mode: SingleAnimationFillMode,
72 play_state: SingleAnimationPlayState,
73 name: NoneOr<KeyframesName>,
74 timeline: SingleAnimationTimeline
75 );
76 Ok(Self { duration, easing, delay, iteration_count, direction, fill_mode, play_state, name, timeline })
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::CssAtomSet;
84 use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
85
86 #[test]
87 fn test_writes() {
88 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "slidein");
89 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "3s");
90 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "auto");
91 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "3s slidein");
92 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "3s 1s slidein");
93 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "3s ease-in 1s infinite alternate both paused slidein");
94 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "slidein 3s ease-in 1s 2 reverse forwards running");
95 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "3s auto");
96 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "\"slide in\"");
97 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "cubic-bezier(0.1,0.7,1,0.1) 3s");
98 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "calc(1s + 200ms) var(--delay) slidein");
99 }
100
101 #[test]
102 fn test_fields() {
103 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "3s 1s slidein", |node| {
104 assert!(matches!(node.duration, Some(AutoOr::Some(_))));
105 assert!(node.delay.is_some());
106 assert!(matches!(node.name, Some(NoneOr::Some(_))));
107 assert!(node.easing.is_none());
108 });
109 assert_parse!(CssAtomSet::ATOMS, SingleAnimation, "auto none", |node| {
110 assert!(matches!(node.duration, Some(AutoOr::Auto(_))));
111 assert!(matches!(node.fill_mode, Some(SingleAnimationFillMode::None(_))));
112 });
113 }
114
115 #[test]
116 fn test_errors() {
117 assert_peek_false!(CssAtomSet::ATOMS, SingleAnimation, "1deg");
118 assert_parse_error!(CssAtomSet::ATOMS, SingleAnimation, "slidein slideout");
119 assert_parse_error!(CssAtomSet::ATOMS, SingleAnimation, "3s 1s 2s");
120 }
121
122 #[test]
123 #[cfg(feature = "visitable")]
124 fn test_visits() {
125 use crate::assert_visits;
126 assert_visits!("3s", SingleAnimation, Time);
127 assert_visits!("ease-in", SingleAnimation, EasingFunction);
128 assert_visits!("3s ease-in 1s", SingleAnimation, Time, EasingFunction, Time);
129 assert_visits!("slidein", SingleAnimation, KeyframesName);
130 assert_visits!("reverse", SingleAnimation, SingleAnimationDirection);
131 assert_visits!("3s slidein forwards", SingleAnimation, Time, SingleAnimationFillMode, KeyframesName);
132 }
133}