1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{NumericValue, ToTyped, TypedValue, UnitValue};
10use crate::values::computed::time::Time as ComputedTime;
11use crate::values::computed::{Context, ToComputedValue};
12use crate::values::specified::calc::{CalcNode, CalcNumeric, Leaf};
13use crate::values::tagged_numeric::{NumericUnion, Unpacked};
14use crate::values::CSSFloat;
15use crate::Zero;
16use cssparser::{match_ignore_ascii_case, Parser, Token};
17use std::fmt::{self, Write};
18use style_traits::values::specified::AllowedNumericType;
19use style_traits::{
20 CssString, CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
21};
22use thin_vec::ThinVec;
23
24#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
26#[repr(u8)]
27pub enum TimeUnit {
28 Second,
30 Millisecond,
32}
33
34#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
36#[repr(C)]
37pub struct NoCalcTime {
38 unit: TimeUnit,
39 value: CSSFloat,
40}
41
42impl NoCalcTime {
43 #[inline]
45 pub fn new(unit: TimeUnit, value: CSSFloat) -> Self {
46 Self { unit, value }
47 }
48
49 #[inline]
51 pub fn from_seconds(seconds: CSSFloat) -> Self {
52 Self::new(TimeUnit::Second, seconds)
53 }
54
55 #[inline]
57 pub fn seconds(&self) -> CSSFloat {
58 match self.unit {
59 TimeUnit::Second => self.value,
60 TimeUnit::Millisecond => self.value / 1000.0,
61 }
62 }
63
64 #[inline]
66 pub fn time_unit(&self) -> TimeUnit {
67 self.unit
68 }
69
70 #[inline]
72 pub fn unit(&self) -> &'static str {
73 match self.unit {
74 TimeUnit::Second => "s",
75 TimeUnit::Millisecond => "ms",
76 }
77 }
78
79 #[inline]
81 pub fn unitless_value(&self) -> CSSFloat {
82 self.value
83 }
84
85 pub fn canonical_unit(&self) -> Option<&'static str> {
87 Some("s")
88 }
89
90 pub fn to(&self, unit: &str) -> Result<Self, ()> {
92 let target = match_ignore_ascii_case! { unit,
93 "s" => TimeUnit::Second,
94 "ms" => TimeUnit::Millisecond,
95 _ => return Err(()),
96 };
97 let value = match target {
98 TimeUnit::Second => self.seconds(),
99 TimeUnit::Millisecond => self.seconds() * 1000.0,
100 };
101 Ok(Self::new(target, value))
102 }
103
104 pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Self, ()> {
106 let unit = match_ignore_ascii_case! { unit,
107 "s" => TimeUnit::Second,
108 "ms" => TimeUnit::Millisecond,
109 _ => return Err(())
110 };
111 Ok(Self::new(unit, value))
112 }
113}
114
115impl ToCss for NoCalcTime {
116 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
117 where
118 W: Write,
119 {
120 crate::values::serialize_specified_dimension(
121 self.unitless_value(),
122 self.unit(),
123 false,
124 dest,
125 )
126 }
127}
128
129impl ToComputedValue for NoCalcTime {
130 type ComputedValue = ComputedTime;
131
132 fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
133 ComputedTime::from_seconds(self.seconds())
134 }
135
136 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
137 Self::from_seconds(computed.seconds())
138 }
139}
140
141impl ToTyped for NoCalcTime {
142 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
143 let numeric_value = NumericValue::Unit(UnitValue {
144 value: self.unitless_value(),
145 unit: CssString::from(self.unit()),
146 });
147
148 dest.push(TypedValue::Numeric(numeric_value));
150
151 Ok(())
152 }
153}
154
155impl SpecifiedValueInfo for NoCalcTime {}
156
157#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
159pub struct Time(NumericUnion<TimeUnit, f32, CalcNumeric>);
160
161impl ToCss for Time {
162 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
163 where
164 W: Write,
165 {
166 match self.0.unpack() {
167 Unpacked::Inline(unit, value) => NoCalcTime::new(unit, value).to_css(dest),
168 Unpacked::Boxed(calc) => calc.to_css(dest),
169 }
170 }
171}
172
173impl ToTyped for Time {
174 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
175 match self.0.unpack() {
176 Unpacked::Inline(unit, value) => NoCalcTime::new(unit, value).to_typed(dest),
177 Unpacked::Boxed(calc) => calc.to_typed(dest),
178 }
179 }
180}
181
182impl SpecifiedValueInfo for Time {}
183
184impl Time {
185 #[inline]
187 pub fn new(time: NoCalcTime) -> Self {
188 Self(NumericUnion::inline(time.unit, time.value))
189 }
190
191 #[inline]
193 pub fn new_calc(calc: Box<CalcNumeric>) -> Self {
194 Self(NumericUnion::boxed(calc))
195 }
196
197 #[inline]
199 pub fn from_seconds(seconds: CSSFloat) -> Self {
200 Self::new(NoCalcTime::from_seconds(seconds))
201 }
202
203 #[inline]
205 pub fn is_calc(&self) -> bool {
206 self.0.is_boxed()
207 }
208
209 fn parse_with_clamping_mode<'i, 't>(
210 context: &ParserContext,
211 input: &mut Parser<'i, 't>,
212 clamping_mode: AllowedNumericType,
213 ) -> Result<Self, ParseError<'i>> {
214 use style_traits::ParsingMode;
215
216 let location = input.current_source_location();
217 match *input.next()? {
218 Token::Dimension {
224 value, ref unit, ..
225 } if clamping_mode.is_ok(ParsingMode::DEFAULT, value) => {
226 NoCalcTime::parse_dimension(value, unit)
227 .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
228 .map(Self::new)
229 },
230 Token::Function(ref name) => {
231 let function = CalcNode::math_function(context, name, location)?;
232 CalcNode::parse_time(context, input, clamping_mode, function)
233 .map(Box::new)
234 .map(Self::new_calc)
235 },
236 ref t => return Err(location.new_unexpected_token_error(t.clone())),
237 }
238 }
239
240 pub fn parse_non_negative<'i, 't>(
242 context: &ParserContext,
243 input: &mut Parser<'i, 't>,
244 ) -> Result<Self, ParseError<'i>> {
245 Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
246 }
247}
248
249impl Zero for Time {
250 #[inline]
251 fn zero() -> Self {
252 Self::from_seconds(0.0)
253 }
254
255 #[inline]
256 fn is_zero(&self) -> bool {
257 match self.0.unpack() {
259 Unpacked::Inline(_, value) => value == 0.0,
260 Unpacked::Boxed(_) => false,
261 }
262 }
263}
264
265impl ToComputedValue for Time {
266 type ComputedValue = ComputedTime;
267
268 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
269 match self.0.unpack() {
270 Unpacked::Inline(unit, value) => {
271 NoCalcTime::new(unit, value).to_computed_value(context)
272 },
273 Unpacked::Boxed(calc) => {
274 let value = calc.resolve(context, |result| match result {
275 Ok(Leaf::Time(t)) => t.seconds(),
276 _ => {
277 debug_assert!(false, "Unexpected Time::Calc without resolved time");
278 f32::NAN
279 },
280 });
281 ComputedTime::from_seconds(
282 crate::values::normalize(value).min(f32::MAX).max(f32::MIN),
283 )
284 },
285 }
286 }
287
288 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
289 Self::from_seconds(computed.seconds())
290 }
291}
292
293impl Parse for Time {
294 fn parse<'i, 't>(
295 context: &ParserContext,
296 input: &mut Parser<'i, 't>,
297 ) -> Result<Self, ParseError<'i>> {
298 Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
299 }
300}