| //! CSS time values. |
| |
| use super::angle::impl_try_from_angle; |
| use super::calc::Calc; |
| use super::number::CSSNumber; |
| use crate::error::{ParserError, PrinterError}; |
| use crate::printer::Printer; |
| use crate::traits::private::AddInternal; |
| use crate::traits::{impl_op, Map, Op, Parse, Sign, ToCss, Zero}; |
| #[cfg(feature = "visitor")] |
| use crate::visitor::Visit; |
| use cssparser::*; |
| |
| /// A CSS [`<time>`](https://www.w3.org/TR/css-values-4/#time) value, in either |
| /// seconds or milliseconds. |
| /// |
| /// Time values may be explicit or computed by `calc()`, but are always stored and serialized |
| /// as their computed value. |
| #[derive(Debug, Clone, PartialEq)] |
| #[cfg_attr(feature = "visitor", derive(Visit))] |
| #[cfg_attr(feature = "visitor", visit(visit_time, TIMES))] |
| #[cfg_attr( |
| feature = "serde", |
| derive(serde::Serialize, serde::Deserialize), |
| serde(tag = "type", content = "value", rename_all = "kebab-case") |
| )] |
| #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] |
| #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] |
| pub enum Time { |
| /// A time in seconds. |
| Seconds(CSSNumber), |
| /// A time in milliseconds. |
| Milliseconds(CSSNumber), |
| } |
| |
| impl Time { |
| /// Returns the time in milliseconds. |
| pub fn to_ms(&self) -> CSSNumber { |
| match self { |
| Time::Seconds(s) => s * 1000.0, |
| Time::Milliseconds(ms) => *ms, |
| } |
| } |
| } |
| |
| impl Zero for Time { |
| fn zero() -> Self { |
| Time::Milliseconds(0.0) |
| } |
| |
| fn is_zero(&self) -> bool { |
| match self { |
| Time::Seconds(s) => s.is_zero(), |
| Time::Milliseconds(s) => s.is_zero(), |
| } |
| } |
| } |
| |
| impl<'i> Parse<'i> for Time { |
| fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> { |
| match input.try_parse(Calc::parse) { |
| Ok(Calc::Value(v)) => return Ok(*v), |
| // Time is always compatible, so they will always compute to a value. |
| Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)), |
| _ => {} |
| } |
| |
| let location = input.current_source_location(); |
| match *input.next()? { |
| Token::Dimension { value, ref unit, .. } => { |
| match_ignore_ascii_case! { unit, |
| "s" => Ok(Time::Seconds(value)), |
| "ms" => Ok(Time::Milliseconds(value)), |
| _ => Err(location.new_unexpected_token_error(Token::Ident(unit.clone()))) |
| } |
| } |
| ref t => Err(location.new_unexpected_token_error(t.clone())), |
| } |
| } |
| } |
| |
| impl<'i> TryFrom<&Token<'i>> for Time { |
| type Error = (); |
| |
| fn try_from(token: &Token) -> Result<Self, Self::Error> { |
| match token { |
| Token::Dimension { value, ref unit, .. } => match_ignore_ascii_case! { unit, |
| "s" => Ok(Time::Seconds(*value)), |
| "ms" => Ok(Time::Milliseconds(*value)), |
| _ => Err(()), |
| }, |
| _ => Err(()), |
| } |
| } |
| } |
| |
| impl ToCss for Time { |
| fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> |
| where |
| W: std::fmt::Write, |
| { |
| // 0.1s is shorter than 100ms |
| // anything smaller is longer |
| match self { |
| Time::Seconds(s) => { |
| if *s > 0.0 && *s < 0.1 { |
| (*s * 1000.0).to_css(dest)?; |
| dest.write_str("ms") |
| } else { |
| s.to_css(dest)?; |
| dest.write_str("s") |
| } |
| } |
| Time::Milliseconds(ms) => { |
| if *ms == 0.0 || *ms >= 100.0 { |
| (*ms / 1000.0).to_css(dest)?; |
| dest.write_str("s") |
| } else { |
| ms.to_css(dest)?; |
| dest.write_str("ms") |
| } |
| } |
| } |
| } |
| } |
| |
| impl std::convert::Into<Calc<Time>> for Time { |
| fn into(self) -> Calc<Time> { |
| Calc::Value(Box::new(self)) |
| } |
| } |
| |
| impl std::convert::TryFrom<Calc<Time>> for Time { |
| type Error = (); |
| |
| fn try_from(calc: Calc<Time>) -> Result<Time, Self::Error> { |
| match calc { |
| Calc::Value(v) => Ok(*v), |
| _ => Err(()), |
| } |
| } |
| } |
| |
| impl std::ops::Mul<f32> for Time { |
| type Output = Self; |
| |
| fn mul(self, other: f32) -> Time { |
| match self { |
| Time::Seconds(t) => Time::Seconds(t * other), |
| Time::Milliseconds(t) => Time::Milliseconds(t * other), |
| } |
| } |
| } |
| |
| impl AddInternal for Time { |
| fn add(self, other: Self) -> Self { |
| self + other |
| } |
| } |
| |
| impl std::cmp::PartialOrd<Time> for Time { |
| fn partial_cmp(&self, other: &Time) -> Option<std::cmp::Ordering> { |
| self.to_ms().partial_cmp(&other.to_ms()) |
| } |
| } |
| |
| impl Op for Time { |
| fn op<F: FnOnce(f32, f32) -> f32>(&self, to: &Self, op: F) -> Self { |
| match (self, to) { |
| (Time::Seconds(a), Time::Seconds(b)) => Time::Seconds(op(*a, *b)), |
| (Time::Milliseconds(a), Time::Milliseconds(b)) => Time::Milliseconds(op(*a, *b)), |
| (Time::Seconds(a), Time::Milliseconds(b)) => Time::Seconds(op(*a, b / 1000.0)), |
| (Time::Milliseconds(a), Time::Seconds(b)) => Time::Milliseconds(op(*a, b * 1000.0)), |
| } |
| } |
| |
| fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> T { |
| match (self, rhs) { |
| (Time::Seconds(a), Time::Seconds(b)) => op(*a, *b), |
| (Time::Milliseconds(a), Time::Milliseconds(b)) => op(*a, *b), |
| (Time::Seconds(a), Time::Milliseconds(b)) => op(*a, b / 1000.0), |
| (Time::Milliseconds(a), Time::Seconds(b)) => op(*a, b * 1000.0), |
| } |
| } |
| } |
| |
| impl Map for Time { |
| fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self { |
| match self { |
| Time::Seconds(t) => Time::Seconds(op(*t)), |
| Time::Milliseconds(t) => Time::Milliseconds(op(*t)), |
| } |
| } |
| } |
| |
| impl Sign for Time { |
| fn sign(&self) -> f32 { |
| match self { |
| Time::Seconds(v) | Time::Milliseconds(v) => v.sign(), |
| } |
| } |
| } |
| |
| impl_op!(Time, std::ops::Rem, rem); |
| impl_op!(Time, std::ops::Add, add); |
| |
| impl_try_from_angle!(Time); |