1use crate::error::{ParserError, PrinterError};
4use crate::printer::Printer;
5use crate::traits::{Parse, ToCss};
6use crate::values::number::{CSSInteger, CSSNumber};
7#[cfg(feature = "visitor")]
8use crate::visitor::Visit;
9use cssparser::*;
10use std::fmt::Write;
11
12#[derive(Debug, Clone, PartialEq)]
14#[cfg_attr(feature = "visitor", derive(Visit))]
15#[cfg_attr(
16 feature = "serde",
17 derive(serde::Serialize, serde::Deserialize),
18 serde(tag = "type", rename_all = "kebab-case")
19)]
20#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
21#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
22pub enum EasingFunction {
23 Linear,
25 Ease,
27 EaseIn,
29 EaseOut,
31 EaseInOut,
33 CubicBezier {
35 x1: CSSNumber,
37 y1: CSSNumber,
39 x2: CSSNumber,
41 y2: CSSNumber,
43 },
44 Steps {
46 count: CSSInteger,
48 #[cfg_attr(feature = "serde", serde(default))]
50 position: StepPosition,
51 },
52}
53
54impl EasingFunction {
55 pub fn is_ease(&self) -> bool {
57 *self == EasingFunction::Ease
58 || *self
59 == EasingFunction::CubicBezier {
60 x1: 0.25,
61 y1: 0.1,
62 x2: 0.25,
63 y2: 1.0,
64 }
65 }
66}
67
68impl<'i> Parse<'i> for EasingFunction {
69 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
70 let location = input.current_source_location();
71 if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
72 let keyword = match_ignore_ascii_case! { &ident,
73 "linear" => EasingFunction::Linear,
74 "ease" => EasingFunction::Ease,
75 "ease-in" => EasingFunction::EaseIn,
76 "ease-out" => EasingFunction::EaseOut,
77 "ease-in-out" => EasingFunction::EaseInOut,
78 "step-start" => EasingFunction::Steps { count: 1, position: StepPosition::Start },
79 "step-end" => EasingFunction::Steps { count: 1, position: StepPosition::End },
80 _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
81 };
82 return Ok(keyword);
83 }
84
85 let function = input.expect_function()?.clone();
86 input.parse_nested_block(|input| {
87 match_ignore_ascii_case! { &function,
88 "cubic-bezier" => {
89 let x1 = CSSNumber::parse(input)?;
90 input.expect_comma()?;
91 let y1 = CSSNumber::parse(input)?;
92 input.expect_comma()?;
93 let x2 = CSSNumber::parse(input)?;
94 input.expect_comma()?;
95 let y2 = CSSNumber::parse(input)?;
96 Ok(EasingFunction::CubicBezier { x1, y1, x2, y2 })
97 },
98 "steps" => {
99 let count = CSSInteger::parse(input)?;
100 let position = input.try_parse(|input| {
101 input.expect_comma()?;
102 StepPosition::parse(input)
103 }).unwrap_or_default();
104 Ok(EasingFunction::Steps { count, position })
105 },
106 _ => return Err(location.new_unexpected_token_error(Token::Ident(function.clone())))
107 }
108 })
109 }
110}
111
112impl ToCss for EasingFunction {
113 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
114 where
115 W: std::fmt::Write,
116 {
117 match self {
118 EasingFunction::Linear => dest.write_str("linear"),
119 EasingFunction::Ease => dest.write_str("ease"),
120 EasingFunction::EaseIn => dest.write_str("ease-in"),
121 EasingFunction::EaseOut => dest.write_str("ease-out"),
122 EasingFunction::EaseInOut => dest.write_str("ease-in-out"),
123 _ if self.is_ease() => dest.write_str("ease"),
124 x if *x
125 == EasingFunction::CubicBezier {
126 x1: 0.42,
127 y1: 0.0,
128 x2: 1.0,
129 y2: 1.0,
130 } =>
131 {
132 dest.write_str("ease-in")
133 }
134 x if *x
135 == EasingFunction::CubicBezier {
136 x1: 0.0,
137 y1: 0.0,
138 x2: 0.58,
139 y2: 1.0,
140 } =>
141 {
142 dest.write_str("ease-out")
143 }
144 x if *x
145 == EasingFunction::CubicBezier {
146 x1: 0.42,
147 y1: 0.0,
148 x2: 0.58,
149 y2: 1.0,
150 } =>
151 {
152 dest.write_str("ease-in-out")
153 }
154 EasingFunction::CubicBezier { x1, y1, x2, y2 } => {
155 dest.write_str("cubic-bezier(")?;
156 x1.to_css(dest)?;
157 dest.delim(',', false)?;
158 y1.to_css(dest)?;
159 dest.delim(',', false)?;
160 x2.to_css(dest)?;
161 dest.delim(',', false)?;
162 y2.to_css(dest)?;
163 dest.write_char(')')
164 }
165 EasingFunction::Steps {
166 count: 1,
167 position: StepPosition::Start,
168 } => dest.write_str("step-start"),
169 EasingFunction::Steps {
170 count: 1,
171 position: StepPosition::End,
172 } => dest.write_str("step-end"),
173 EasingFunction::Steps { count, position } => {
174 dest.write_str("steps(")?;
175 write!(dest, "{}", count)?;
176 dest.delim(',', false)?;
177 position.to_css(dest)?;
178 dest.write_char(')')
179 }
180 }
181 }
182}
183
184impl EasingFunction {
185 pub fn is_ident(s: &str) -> bool {
187 match s {
188 "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" | "step-start" | "step-end" => true,
189 _ => false,
190 }
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, ToCss)]
196#[cfg_attr(feature = "visitor", derive(Visit))]
197#[cfg_attr(
198 feature = "serde",
199 derive(serde::Serialize, serde::Deserialize),
200 serde(tag = "type", content = "value", rename_all = "kebab-case")
201)]
202#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
203pub enum StepPosition {
204 Start,
206 End,
208 JumpNone,
210 JumpBoth,
212}
213
214impl Default for StepPosition {
215 fn default() -> Self {
216 StepPosition::End
217 }
218}
219
220impl<'i> Parse<'i> for StepPosition {
221 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
222 let location = input.current_source_location();
223 let ident = input.expect_ident()?;
224 let keyword = match_ignore_ascii_case! { &ident,
225 "start" => StepPosition::Start,
226 "end" => StepPosition::End,
227 "jump-start" => StepPosition::Start,
228 "jump-end" => StepPosition::End,
229 "jump-none" => StepPosition::JumpNone,
230 "jump-both" => StepPosition::JumpBoth,
231 _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
232 };
233 Ok(keyword)
234 }
235}