Skip to main content

css_ast/functions/
easing_functions.rs

1use super::prelude::*;
2use crate::{CalcableValue, NumericValue, Percentage};
3use css_parse::Box;
4
5/// <https://drafts.csswg.org/css-easing-2/#typedef-easing-function>
6///
7/// ```text,ignore
8/// <easing-function> = <linear-easing-function>
9///                      | <cubic-bezier-easing-function>
10///                      | <step-easing-function>
11///
12/// <linear-easing-function> = linear | <linear()>
13///
14/// linear() = linear( [ <number> && <percentage>{0,2} ]# )
15///
16/// <cubic-bezier-easing-function> = ease | ease-in | ease-out | ease-in-out
17///                                    | <cubic-bezier()>
18///
19/// cubic-bezier() = cubic-bezier( [ <number [0,1]>, <number> ]#{2} )
20///
21/// <step-easing-function> = step-start | step-end | <steps()>
22///
23/// steps() = steps( <integer>, <step-position>?)
24///
25/// <step-position> = jump-start | jump-end | jump-none | jump-both | start | end
26/// ```
27#[node]
28#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
30#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
31#[derive(csskit_derives::NodeWithMetadata)]
32pub enum EasingFunction<'a> {
33	#[cfg_attr(feature = "visitable", visit(skip))]
34	#[atom(CssAtomSet::Linear)]
35	Linear(T![Ident]),
36	#[cfg_attr(feature = "visitable", visit(skip))]
37	#[atom(CssAtomSet::Ease)]
38	Ease(T![Ident]),
39	#[cfg_attr(feature = "visitable", visit(skip))]
40	#[atom(CssAtomSet::EaseIn)]
41	EaseIn(T![Ident]),
42	#[cfg_attr(feature = "visitable", visit(skip))]
43	#[atom(CssAtomSet::EaseOut)]
44	EaseOut(T![Ident]),
45	#[cfg_attr(feature = "visitable", visit(skip))]
46	#[atom(CssAtomSet::EaseInOut)]
47	EaseInOut(T![Ident]),
48	#[cfg_attr(feature = "visitable", visit(skip))]
49	#[atom(CssAtomSet::StepStart)]
50	StepStart(T![Ident]),
51	#[cfg_attr(feature = "visitable", visit(skip))]
52	#[atom(CssAtomSet::StepEnd)]
53	StepEnd(T![Ident]),
54	#[atom(CssAtomSet::Linear)]
55	LinearFunction(LinearFunction<'a>),
56	#[atom(CssAtomSet::CubicBezier)]
57	CubicBezierFunction(Box<'a, CubicBezierFunction<'a>>),
58	#[atom(CssAtomSet::Steps)]
59	StepsFunction(StepsFunction<'a>),
60}
61
62#[node]
63#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
65#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
66#[derive(csskit_derives::NodeWithMetadata)]
67pub struct LinearFunction<'a> {
68	#[atom(CssAtomSet::Linear)]
69	pub name: T![Function],
70	pub params: CommaSeparated<'a, LinearFunctionParams<'a>>,
71	#[semantic_eq(skip)]
72	pub close: T![')'],
73}
74
75#[node]
76#[derive(Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
78#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
79#[derive(csskit_derives::NodeWithMetadata)]
80pub struct LinearFunctionParams<'a>(
81	pub NumericValue<'a, T![Number]>,
82	pub Option<CalcableValue<'a, Percentage>>,
83	pub Option<CalcableValue<'a, Percentage>>,
84);
85
86impl<'a> Parse<'a> for LinearFunctionParams<'a> {
87	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
88	where
89		I: Iterator<Item = Cursor> + Clone,
90	{
91		let mut num = p.parse_if_peek::<NumericValue<T![Number]>>()?;
92		let percent = p.parse_if_peek::<CalcableValue<Percentage>>()?;
93		let percent2 = p.parse_if_peek::<CalcableValue<Percentage>>()?;
94		if num.is_none() {
95			num = Some(p.parse::<NumericValue<T![Number]>>()?);
96		}
97		Ok(Self(num.unwrap(), percent, percent2))
98	}
99}
100
101#[node]
102#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
104#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
105#[derive(csskit_derives::NodeWithMetadata)]
106pub struct CubicBezierFunction<'a> {
107	#[atom(CssAtomSet::CubicBezier)]
108	pub name: T![Function],
109	pub params: CubicBezierFunctionParams<'a>,
110	#[semantic_eq(skip)]
111	pub close: T![')'],
112}
113
114#[node]
115#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
117#[derive(csskit_derives::NodeWithMetadata)]
118pub struct CubicBezierFunctionParams<'a> {
119	pub x1: NumericValue<'a, T![Number]>,
120	#[semantic_eq(skip)]
121	pub c1: Option<T![,]>,
122	pub y1: NumericValue<'a, T![Number]>,
123	#[semantic_eq(skip)]
124	pub c2: Option<T![,]>,
125	pub x2: NumericValue<'a, T![Number]>,
126	#[semantic_eq(skip)]
127	pub c3: Option<T![,]>,
128	pub y2: NumericValue<'a, T![Number]>,
129}
130
131#[node]
132#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
134#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
135#[derive(csskit_derives::NodeWithMetadata)]
136pub struct StepsFunction<'a> {
137	#[atom(CssAtomSet::Steps)]
138	pub name: T![Function],
139	pub params: StepsFunctionParams<'a>,
140	#[semantic_eq(skip)]
141	pub close: T![')'],
142}
143
144#[node]
145#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
147#[derive(csskit_derives::NodeWithMetadata)]
148pub struct StepsFunctionParams<'a>(
149	pub NumericValue<'a, CSSInt>,
150	#[semantic_eq(skip)] pub Option<T![,]>,
151	pub Option<StepPosition>,
152);
153
154#[node]
155#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
156#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
157#[derive(csskit_derives::NodeWithMetadata)]
158pub enum StepPosition {
159	#[atom(CssAtomSet::JumpStart)]
160	JumpStart(T![Ident]),
161	#[atom(CssAtomSet::JumpEnd)]
162	JumpEnd(T![Ident]),
163	#[atom(CssAtomSet::JumpNone)]
164	JumpNone(T![Ident]),
165	#[atom(CssAtomSet::JumpBoth)]
166	JumpBoth(T![Ident]),
167	#[atom(CssAtomSet::Start)]
168	Start(T![Ident]),
169	#[atom(CssAtomSet::End)]
170	End(T![Ident]),
171}
172
173#[cfg(test)]
174mod tests {
175	use super::*;
176	use crate::CssAtomSet;
177	use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
178
179	#[test]
180	fn test_writes() {
181		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "ease-in-out");
182		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "linear(0,1)");
183		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "linear(0,0.25,1)");
184		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "linear(0,0.5 25% 75%,1)");
185		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "cubic-bezier(0.25,0.1,0.25,1)");
186		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "cubic-bezier(0.1,-0.6,0.2,0)");
187		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "cubic-bezier(0,0,1,1)");
188		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "steps(4,end)");
189		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "steps(10,jump-both)");
190		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "linear(0,0.25,1)");
191		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "cubic-bezier(0.1 -0.6 0.2 0)");
192	}
193
194	#[test]
195	fn test_substitution() {
196		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "linear(var(--a),1)");
197		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "cubic-bezier(calc(0.1 + 0.1),0.1,0.25,var(--y))");
198		assert_parse!(CssAtomSet::ATOMS, EasingFunction, "steps(calc(2 + 2),end)");
199	}
200
201	#[test]
202	fn test_errors() {
203		assert_peek_false!(CssAtomSet::ATOMS, EasingFunction, "foo");
204		assert_parse_error!(CssAtomSet::ATOMS, EasingFunction, "linear()");
205		assert_parse_error!(CssAtomSet::ATOMS, EasingFunction, "cubic-bezier(0.1, red, 1.0, green)");
206	}
207}