1
//! CSS property types for time durations (s, ms).
2

            
3
use alloc::string::{String, ToString};
4
use crate::corety::AzString;
5

            
6
use crate::props::formatter::PrintAsCssValue;
7

            
8
/// A CSS time duration, stored internally in milliseconds.
9
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10
#[repr(C)]
11
#[derive(Default)]
12
pub struct CssDuration {
13
    /// Duration in milliseconds.
14
    pub inner: u32,
15
}
16

            
17

            
18
impl PrintAsCssValue for CssDuration {
19
    fn print_as_css_value(&self) -> String {
20
        format!("{}ms", self.inner)
21
    }
22
}
23

            
24
impl crate::format_rust_code::FormatAsRustCode for CssDuration {
25
    fn format_as_rust_code(&self, _tabs: usize) -> String {
26
        format!("CssDuration {{ inner: {} }}", self.inner)
27
    }
28
}
29

            
30
/// Error returned when parsing a CSS duration string fails.
31
#[cfg(feature = "parser")]
32
#[derive(Clone, PartialEq)]
33
pub enum DurationParseError<'a> {
34
    InvalidValue(&'a str),
35
    ParseFloat(core::num::ParseFloatError),
36
}
37

            
38
#[cfg(feature = "parser")]
39
impl_debug_as_display!(DurationParseError<'a>);
40
#[cfg(feature = "parser")]
41
impl_display! { DurationParseError<'a>, {
42
    InvalidValue(v) => format!("Invalid time value: \"{}\"", v),
43
    ParseFloat(e) => format!("Invalid number for time value: {}", e),
44
}}
45

            
46
/// Owned version of [`DurationParseError`] for FFI and storage.
47
#[cfg(feature = "parser")]
48
#[derive(Debug, Clone, PartialEq)]
49
#[repr(C, u8)]
50
pub enum DurationParseErrorOwned {
51
    InvalidValue(AzString),
52
    ParseFloat(AzString),
53
}
54

            
55
#[cfg(feature = "parser")]
56
impl<'a> DurationParseError<'a> {
57
    pub fn to_contained(&self) -> DurationParseErrorOwned {
58
        match self {
59
            Self::InvalidValue(s) => DurationParseErrorOwned::InvalidValue(s.to_string().into()),
60
            Self::ParseFloat(e) => DurationParseErrorOwned::ParseFloat(e.to_string().into()),
61
        }
62
    }
63
}
64

            
65
#[cfg(feature = "parser")]
66
impl DurationParseErrorOwned {
67
    pub fn to_shared<'a>(&'a self) -> DurationParseError<'a> {
68
        match self {
69
            Self::InvalidValue(s) => DurationParseError::InvalidValue(s),
70
            Self::ParseFloat(s) => DurationParseError::InvalidValue(s.as_str()),
71
        }
72
    }
73
}
74

            
75
/// Parses a CSS duration string (e.g. `"200ms"`, `"1.5s"`) into a [`CssDuration`].
76
#[cfg(feature = "parser")]
77
pub fn parse_duration<'a>(input: &'a str) -> Result<CssDuration, DurationParseError<'a>> {
78
    let trimmed = input.trim().to_lowercase();
79
    if trimmed == "0" {
80
        return Ok(CssDuration { inner: 0 });
81
    }
82
    if let Some(num_str) = trimmed.strip_suffix("ms") {
83
        let ms = num_str
84
            .parse::<f32>()
85
            .map_err(DurationParseError::ParseFloat)?;
86
        if ms < 0.0 {
87
            return Err(DurationParseError::InvalidValue(input));
88
        }
89
        Ok(CssDuration { inner: ms as u32 })
90
    } else if let Some(num_str) = trimmed.strip_suffix('s') {
91
        let s = num_str
92
            .parse::<f32>()
93
            .map_err(DurationParseError::ParseFloat)?;
94
        if s < 0.0 {
95
            return Err(DurationParseError::InvalidValue(input));
96
        }
97
        Ok(CssDuration {
98
            inner: (s * 1000.0) as u32,
99
        })
100
    } else {
101
        Err(DurationParseError::InvalidValue(input))
102
    }
103
}