lightningcss/values/
time.rs

1//! CSS time values.
2
3use super::angle::impl_try_from_angle;
4use super::calc::Calc;
5use super::number::CSSNumber;
6use crate::error::{ParserError, PrinterError};
7use crate::printer::Printer;
8use crate::traits::private::AddInternal;
9use crate::traits::{impl_op, Map, Op, Parse, Sign, ToCss, Zero};
10#[cfg(feature = "visitor")]
11use crate::visitor::Visit;
12use cssparser::*;
13
14/// A CSS [`<time>`](https://www.w3.org/TR/css-values-4/#time) value, in either
15/// seconds or milliseconds.
16///
17/// Time values may be explicit or computed by `calc()`, but are always stored and serialized
18/// as their computed value.
19#[derive(Debug, Clone, PartialEq)]
20#[cfg_attr(feature = "visitor", derive(Visit))]
21#[cfg_attr(feature = "visitor", visit(visit_time, TIMES))]
22#[cfg_attr(
23  feature = "serde",
24  derive(serde::Serialize, serde::Deserialize),
25  serde(tag = "type", content = "value", rename_all = "kebab-case")
26)]
27#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
28#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
29pub enum Time {
30  /// A time in seconds.
31  Seconds(CSSNumber),
32  /// A time in milliseconds.
33  Milliseconds(CSSNumber),
34}
35
36impl Time {
37  /// Returns the time in milliseconds.
38  pub fn to_ms(&self) -> CSSNumber {
39    match self {
40      Time::Seconds(s) => s * 1000.0,
41      Time::Milliseconds(ms) => *ms,
42    }
43  }
44}
45
46impl Zero for Time {
47  fn zero() -> Self {
48    Time::Milliseconds(0.0)
49  }
50
51  fn is_zero(&self) -> bool {
52    match self {
53      Time::Seconds(s) => s.is_zero(),
54      Time::Milliseconds(s) => s.is_zero(),
55    }
56  }
57}
58
59impl<'i> Parse<'i> for Time {
60  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
61    match input.try_parse(Calc::parse) {
62      Ok(Calc::Value(v)) => return Ok(*v),
63      // Time is always compatible, so they will always compute to a value.
64      Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),
65      _ => {}
66    }
67
68    let location = input.current_source_location();
69    match *input.next()? {
70      Token::Dimension { value, ref unit, .. } => {
71        match_ignore_ascii_case! { unit,
72          "s" => Ok(Time::Seconds(value)),
73          "ms" => Ok(Time::Milliseconds(value)),
74          _ => Err(location.new_unexpected_token_error(Token::Ident(unit.clone())))
75        }
76      }
77      ref t => Err(location.new_unexpected_token_error(t.clone())),
78    }
79  }
80}
81
82impl<'i> TryFrom<&Token<'i>> for Time {
83  type Error = ();
84
85  fn try_from(token: &Token) -> Result<Self, Self::Error> {
86    match token {
87      Token::Dimension { value, ref unit, .. } => match_ignore_ascii_case! { unit,
88        "s" => Ok(Time::Seconds(*value)),
89        "ms" => Ok(Time::Milliseconds(*value)),
90        _ => Err(()),
91      },
92      _ => Err(()),
93    }
94  }
95}
96
97impl ToCss for Time {
98  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
99  where
100    W: std::fmt::Write,
101  {
102    // 0.1s is shorter than 100ms
103    // anything smaller is longer
104    match self {
105      Time::Seconds(s) => {
106        if *s > 0.0 && *s < 0.1 {
107          (*s * 1000.0).to_css(dest)?;
108          dest.write_str("ms")
109        } else {
110          s.to_css(dest)?;
111          dest.write_str("s")
112        }
113      }
114      Time::Milliseconds(ms) => {
115        if *ms == 0.0 || *ms >= 100.0 {
116          (*ms / 1000.0).to_css(dest)?;
117          dest.write_str("s")
118        } else {
119          ms.to_css(dest)?;
120          dest.write_str("ms")
121        }
122      }
123    }
124  }
125}
126
127impl std::convert::Into<Calc<Time>> for Time {
128  fn into(self) -> Calc<Time> {
129    Calc::Value(Box::new(self))
130  }
131}
132
133impl std::convert::TryFrom<Calc<Time>> for Time {
134  type Error = ();
135
136  fn try_from(calc: Calc<Time>) -> Result<Time, Self::Error> {
137    match calc {
138      Calc::Value(v) => Ok(*v),
139      _ => Err(()),
140    }
141  }
142}
143
144impl std::ops::Mul<f32> for Time {
145  type Output = Self;
146
147  fn mul(self, other: f32) -> Time {
148    match self {
149      Time::Seconds(t) => Time::Seconds(t * other),
150      Time::Milliseconds(t) => Time::Milliseconds(t * other),
151    }
152  }
153}
154
155impl AddInternal for Time {
156  fn add(self, other: Self) -> Self {
157    self + other
158  }
159}
160
161impl std::cmp::PartialOrd<Time> for Time {
162  fn partial_cmp(&self, other: &Time) -> Option<std::cmp::Ordering> {
163    self.to_ms().partial_cmp(&other.to_ms())
164  }
165}
166
167impl Op for Time {
168  fn op<F: FnOnce(f32, f32) -> f32>(&self, to: &Self, op: F) -> Self {
169    match (self, to) {
170      (Time::Seconds(a), Time::Seconds(b)) => Time::Seconds(op(*a, *b)),
171      (Time::Milliseconds(a), Time::Milliseconds(b)) => Time::Milliseconds(op(*a, *b)),
172      (Time::Seconds(a), Time::Milliseconds(b)) => Time::Seconds(op(*a, b / 1000.0)),
173      (Time::Milliseconds(a), Time::Seconds(b)) => Time::Milliseconds(op(*a, b * 1000.0)),
174    }
175  }
176
177  fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> T {
178    match (self, rhs) {
179      (Time::Seconds(a), Time::Seconds(b)) => op(*a, *b),
180      (Time::Milliseconds(a), Time::Milliseconds(b)) => op(*a, *b),
181      (Time::Seconds(a), Time::Milliseconds(b)) => op(*a, b / 1000.0),
182      (Time::Milliseconds(a), Time::Seconds(b)) => op(*a, b * 1000.0),
183    }
184  }
185}
186
187impl Map for Time {
188  fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {
189    match self {
190      Time::Seconds(t) => Time::Seconds(op(*t)),
191      Time::Milliseconds(t) => Time::Milliseconds(op(*t)),
192    }
193  }
194}
195
196impl Sign for Time {
197  fn sign(&self) -> f32 {
198    match self {
199      Time::Seconds(v) | Time::Milliseconds(v) => v.sign(),
200    }
201  }
202}
203
204impl_op!(Time, std::ops::Rem, rem);
205impl_op!(Time, std::ops::Add, add);
206
207impl_try_from_angle!(Time);