Skip to main content

css_ast/types/
single_animation.rs

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/// <https://drafts.csswg.org/css-animations-2/#typedef-single-animation>
9///
10/// ```text,ignore
11/// <single-animation> =
12///   <'animation-duration'> ||
13///   <easing-function> ||
14///   <'animation-delay-start'> ||
15///   <single-animation-iteration-count> ||
16///   <single-animation-direction> ||
17///   <single-animation-fill-mode> ||
18///   <single-animation-play-state> ||
19///   [ none | <keyframes-name> ] ||
20///   <single-animation-timeline>
21/// ```
22#[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}