1use crate::error::{ParserError, PrinterError};
4use crate::printer::Printer;
5use crate::traits::{Parse, ToCss};
6use crate::values::number::{CSSInteger, CSSNumber};
7use cssparser::*;
8use std::fmt::Write;
9
10#[derive(Debug, Clone, PartialEq)]
12#[cfg_attr(
13 feature = "serde",
14 derive(serde::Serialize, serde::Deserialize),
15 serde(tag = "type", content = "value", rename_all = "kebab-case")
16)]
17pub enum EasingFunction {
18 Linear,
20 Ease,
22 EaseIn,
24 EaseOut,
26 EaseInOut,
28 CubicBezier(CSSNumber, CSSNumber, CSSNumber, CSSNumber),
32 Steps(CSSInteger, StepPosition),
37}
38
39impl<'i> Parse<'i> for EasingFunction {
40 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
41 let location = input.current_source_location();
42 if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
43 let keyword = match_ignore_ascii_case! { &ident,
44 "linear" => EasingFunction::Linear,
45 "ease" => EasingFunction::Ease,
46 "ease-in" => EasingFunction::EaseIn,
47 "ease-out" => EasingFunction::EaseOut,
48 "ease-in-out" => EasingFunction::EaseInOut,
49 "step-start" => EasingFunction::Steps(1, StepPosition::Start),
50 "step-end" => EasingFunction::Steps(1, StepPosition::End),
51 _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
52 };
53 return Ok(keyword);
54 }
55
56 let function = input.expect_function()?.clone();
57 input.parse_nested_block(|input| {
58 match_ignore_ascii_case! { &function,
59 "cubic-bezier" => {
60 let x1 = CSSNumber::parse(input)?;
61 input.expect_comma()?;
62 let y1 = CSSNumber::parse(input)?;
63 input.expect_comma()?;
64 let x2 = CSSNumber::parse(input)?;
65 input.expect_comma()?;
66 let y2 = CSSNumber::parse(input)?;
67 Ok(EasingFunction::CubicBezier(x1, y1, x2, y2))
68 },
69 "steps" => {
70 let steps = CSSInteger::parse(input)?;
71 let position = input.try_parse(|input| {
72 input.expect_comma()?;
73 StepPosition::parse(input)
74 }).unwrap_or(StepPosition::End);
75 Ok(EasingFunction::Steps(steps, position))
76 },
77 _ => return Err(location.new_unexpected_token_error(Token::Ident(function.clone())))
78 }
79 })
80 }
81}
82
83impl ToCss for EasingFunction {
84 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
85 where
86 W: std::fmt::Write,
87 {
88 match self {
89 EasingFunction::Linear => dest.write_str("linear"),
90 EasingFunction::Ease => dest.write_str("ease"),
91 EasingFunction::EaseIn => dest.write_str("ease-in"),
92 EasingFunction::EaseOut => dest.write_str("ease-out"),
93 EasingFunction::EaseInOut => dest.write_str("ease-in-out"),
94 x if *x == EasingFunction::CubicBezier(0.25, 0.1, 0.25, 1.0) => dest.write_str("ease"),
95 x if *x == EasingFunction::CubicBezier(0.42, 0.0, 1.0, 1.0) => dest.write_str("ease-in"),
96 x if *x == EasingFunction::CubicBezier(0.0, 0.0, 0.58, 1.0) => dest.write_str("ease-out"),
97 x if *x == EasingFunction::CubicBezier(0.42, 0.0, 0.58, 1.0) => dest.write_str("ease-in-out"),
98 EasingFunction::CubicBezier(x1, y1, x2, y2) => {
99 dest.write_str("cubic-bezier(")?;
100 x1.to_css(dest)?;
101 dest.delim(',', false)?;
102 y1.to_css(dest)?;
103 dest.delim(',', false)?;
104 x2.to_css(dest)?;
105 dest.delim(',', false)?;
106 y2.to_css(dest)?;
107 dest.write_char(')')
108 }
109 EasingFunction::Steps(1, StepPosition::Start) => dest.write_str("step-start"),
110 EasingFunction::Steps(1, StepPosition::End) => dest.write_str("step-end"),
111 EasingFunction::Steps(steps, position) => {
112 dest.write_str("steps(")?;
113 write!(dest, "{}", steps)?;
114 dest.delim(',', false)?;
115 position.to_css(dest)?;
116 dest.write_char(')')
117 }
118 }
119 }
120}
121
122impl EasingFunction {
123 pub fn is_ident(s: &str) -> bool {
125 match s {
126 "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" | "step-start" | "step-end" => true,
127 _ => false,
128 }
129 }
130}
131
132#[derive(Debug, Clone, PartialEq)]
134#[cfg_attr(
135 feature = "serde",
136 derive(serde::Serialize, serde::Deserialize),
137 serde(tag = "type", content = "value", rename_all = "kebab-case")
138)]
139pub enum StepPosition {
140 Start,
142 End,
144 JumpNone,
146 JumpBoth,
148}
149
150impl<'i> Parse<'i> for StepPosition {
151 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
152 let location = input.current_source_location();
153 let ident = input.expect_ident()?;
154 let keyword = match_ignore_ascii_case! { &ident,
155 "start" => StepPosition::Start,
156 "end" => StepPosition::End,
157 "jump-start" => StepPosition::Start,
158 "jump-end" => StepPosition::End,
159 "jump-none" => StepPosition::JumpNone,
160 "jump-both" => StepPosition::JumpBoth,
161 _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
162 };
163 Ok(keyword)
164 }
165}
166
167impl ToCss for StepPosition {
168 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
169 where
170 W: std::fmt::Write,
171 {
172 match self {
173 StepPosition::Start => dest.write_str("start"),
174 StepPosition::End => dest.write_str("end"),
175 StepPosition::JumpNone => dest.write_str("jump-none"),
176 StepPosition::JumpBoth => dest.write_str("jump-both"),
177 }
178 }
179}