parcel_css/values/
easing.rs

1//! CSS easing functions.
2
3use 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/// A CSS [easing function](https://www.w3.org/TR/css-easing-1/#easing-functions).
11#[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  /// A linear easing function.
19  Linear,
20  /// Equivalent to `cubic-bezier(0.25, 0.1, 0.25, 1)`.
21  Ease,
22  /// Equivalent to `cubic-bezier(0.42, 0, 1, 1)`.
23  EaseIn,
24  /// Equivalent to `cubic-bezier(0, 0, 0.58, 1)`.
25  EaseOut,
26  /// Equivalent to `cubic-bezier(0.42, 0, 0.58, 1)`.
27  EaseInOut,
28  /// A custom cubic Bézier easing function.
29  ///
30  /// The four numbers specify points P1 and P2 of the curve as (x1, y1, x2, y2).
31  CubicBezier(CSSNumber, CSSNumber, CSSNumber, CSSNumber),
32  /// A step easing function.
33  ///
34  /// The first parameter specifies the number of intervals in the function.
35  /// The second parameter specifies the step position.
36  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  /// Returns whether the given string is a valid easing function name.
124  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/// A [step position](https://www.w3.org/TR/css-easing-1/#step-position), used within the `steps()` function.
133#[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  /// The first rise occurs at input progress value of 0.
141  Start,
142  /// The last rise occurs at input progress value of 1.
143  End,
144  /// All rises occur within the range (0, 1).
145  JumpNone,
146  /// The first rise occurs at input progress value of 0 and the last rise occurs at input progress value of 1.
147  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}