1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::values::computed::time::Time as ComputedTime;
10use crate::values::computed::{Context, ToComputedValue};
11use crate::values::specified::calc::CalcNode;
12use crate::values::CSSFloat;
13use crate::Zero;
14use cssparser::{match_ignore_ascii_case, Parser, Token};
15use std::fmt::{self, Write};
16use style_traits::values::specified::AllowedNumericType;
17use style_traits::{
18 CssString, CssWriter, MathSum, NumericValue, ParseError, SpecifiedValueInfo,
19 StyleParseErrorKind, ToCss, ToTyped, TypedValue, UnitValue,
20};
21use thin_vec::ThinVec;
22
23#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
25pub struct Time {
26 seconds: CSSFloat,
27 unit: TimeUnit,
28 calc_clamping_mode: Option<AllowedNumericType>,
29}
30
31#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
33pub enum TimeUnit {
34 Second,
36 Millisecond,
38}
39
40impl Time {
41 pub fn from_seconds_with_calc_clamping_mode(
43 seconds: CSSFloat,
44 calc_clamping_mode: Option<AllowedNumericType>,
45 ) -> Self {
46 Time {
47 seconds,
48 unit: TimeUnit::Second,
49 calc_clamping_mode,
50 }
51 }
52
53 pub fn from_seconds(seconds: CSSFloat) -> Self {
55 Self::from_seconds_with_calc_clamping_mode(seconds, None)
56 }
57
58 pub fn seconds(self) -> CSSFloat {
60 self.seconds
61 }
62
63 #[inline]
65 pub fn unit(&self) -> &'static str {
66 match self.unit {
67 TimeUnit::Second => "s",
68 TimeUnit::Millisecond => "ms",
69 }
70 }
71
72 #[inline]
74 pub fn unitless_value(&self) -> CSSFloat {
75 match self.unit {
76 TimeUnit::Second => self.seconds,
77 TimeUnit::Millisecond => self.seconds * 1000.,
78 }
79 }
80
81 pub fn canonical_unit(&self) -> Option<&'static str> {
83 Some("s")
84 }
85
86 pub fn to(&self, unit: &str) -> Result<Self, ()> {
88 let unit = match_ignore_ascii_case! { unit,
89 "s" => TimeUnit::Second,
90 "ms" => TimeUnit::Millisecond,
91 _ => return Err(()),
92 };
93
94 Ok(Time {
95 seconds: self.seconds,
96 unit,
97 calc_clamping_mode: None,
98 })
99 }
100
101 pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Time, ()> {
103 let (seconds, unit) = match_ignore_ascii_case! { unit,
104 "s" => (value, TimeUnit::Second),
105 "ms" => (value / 1000.0, TimeUnit::Millisecond),
106 _ => return Err(())
107 };
108
109 Ok(Time {
110 seconds,
111 unit,
112 calc_clamping_mode: None,
113 })
114 }
115
116 fn parse_with_clamping_mode<'i, 't>(
117 context: &ParserContext,
118 input: &mut Parser<'i, 't>,
119 clamping_mode: AllowedNumericType,
120 ) -> Result<Self, ParseError<'i>> {
121 use style_traits::ParsingMode;
122
123 let location = input.current_source_location();
124 match *input.next()? {
125 Token::Dimension {
131 value, ref unit, ..
132 } if clamping_mode.is_ok(ParsingMode::DEFAULT, value) => {
133 Time::parse_dimension(value, unit)
134 .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
135 },
136 Token::Function(ref name) => {
137 let function = CalcNode::math_function(context, name, location)?;
138 CalcNode::parse_time(context, input, clamping_mode, function)
139 },
140 ref t => return Err(location.new_unexpected_token_error(t.clone())),
141 }
142 }
143
144 pub fn parse_non_negative<'i, 't>(
146 context: &ParserContext,
147 input: &mut Parser<'i, 't>,
148 ) -> Result<Self, ParseError<'i>> {
149 Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
150 }
151}
152
153impl Zero for Time {
154 #[inline]
155 fn zero() -> Self {
156 Self::from_seconds(0.0)
157 }
158
159 #[inline]
160 fn is_zero(&self) -> bool {
161 self.seconds == 0.0 && self.calc_clamping_mode.is_none()
163 }
164}
165
166impl ToComputedValue for Time {
167 type ComputedValue = ComputedTime;
168
169 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
170 let seconds = self
171 .calc_clamping_mode
172 .map_or(self.seconds(), |mode| mode.clamp(self.seconds()));
173
174 ComputedTime::from_seconds(crate::values::normalize(seconds))
175 }
176
177 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
178 Time {
179 seconds: computed.seconds(),
180 unit: TimeUnit::Second,
181 calc_clamping_mode: None,
182 }
183 }
184}
185
186impl Parse for Time {
187 fn parse<'i, 't>(
188 context: &ParserContext,
189 input: &mut Parser<'i, 't>,
190 ) -> Result<Self, ParseError<'i>> {
191 Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
192 }
193}
194
195impl ToCss for Time {
196 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
197 where
198 W: Write,
199 {
200 crate::values::serialize_specified_dimension(
201 self.unitless_value(),
202 self.unit(),
203 self.calc_clamping_mode.is_some(),
204 dest,
205 )
206 }
207}
208
209impl ToTyped for Time {
210 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
211 let numeric_value = NumericValue::Unit(UnitValue {
212 value: self.unitless_value(),
213 unit: CssString::from(self.unit()),
214 });
215
216 if self.calc_clamping_mode.is_some() {
218 dest.push(TypedValue::Numeric(NumericValue::Sum(MathSum {
219 values: ThinVec::from([numeric_value]),
220 })));
221 } else {
222 dest.push(TypedValue::Numeric(numeric_value));
223 }
224
225 Ok(())
226 }
227}
228
229impl SpecifiedValueInfo for Time {}