css_ast/functions/
easing_functions.rs

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