Skip to main content

azul_css/props/style/
border_radius.rs

1//! CSS properties for border radius (`border-top-left-radius`,
2//! `border-top-right-radius`, `border-bottom-left-radius`,
3//! `border-bottom-right-radius`) and the `border-radius` shorthand parser.
4
5use alloc::string::{String, ToString};
6use crate::corety::AzString;
7
8use crate::{
9    props::{
10        basic::pixel::{
11            parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue,
12        },
13        macros::PixelValueTaker,
14    },
15};
16
17// --- Property Struct Definitions ---
18
19macro_rules! define_border_radius_property {
20    ($struct_name:ident) => {
21        #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22        #[repr(C)]
23        pub struct $struct_name {
24            pub inner: PixelValue,
25        }
26
27        impl ::core::fmt::Debug for $struct_name {
28            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
29                write!(f, "{}", self.inner)
30            }
31        }
32
33        impl PixelValueTaker for $struct_name {
34            fn from_pixel_value(inner: PixelValue) -> Self {
35                Self { inner }
36            }
37        }
38
39        impl_pixel_value!($struct_name);
40    };
41}
42
43/// CSS `border-top-left-radius` property value.
44define_border_radius_property!(StyleBorderTopLeftRadius);
45/// CSS `border-top-right-radius` property value.
46define_border_radius_property!(StyleBorderTopRightRadius);
47/// CSS `border-bottom-left-radius` property value.
48define_border_radius_property!(StyleBorderBottomLeftRadius);
49/// CSS `border-bottom-right-radius` property value.
50define_border_radius_property!(StyleBorderBottomRightRadius);
51
52// --- Parser-only Struct ---
53
54/// A temporary struct used only during the parsing of the `border-radius` shorthand property.
55#[cfg(feature = "parser")]
56#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct StyleBorderRadius {
58    pub top_left: PixelValue,
59    pub top_right: PixelValue,
60    pub bottom_left: PixelValue,
61    pub bottom_right: PixelValue,
62}
63
64// --- Error Types ---
65
66/// Error for the shorthand `border-radius` property.
67#[derive(Clone, PartialEq, Eq)]
68pub enum CssBorderRadiusParseError<'a> {
69    /// Too many values were provided (max is 4).
70    TooManyValues(&'a str),
71    /// An underlying pixel value could not be parsed.
72    PixelValue(CssPixelValueParseError<'a>),
73}
74
75impl_debug_as_display!(CssBorderRadiusParseError<'a>);
76impl_display! { CssBorderRadiusParseError<'a>, {
77    TooManyValues(val) => format!("Too many values for border-radius: \"{}\"", val),
78    PixelValue(e) => format!("{}", e),
79}}
80impl_from!(
81    CssPixelValueParseError<'a>,
82    CssBorderRadiusParseError::PixelValue
83);
84
85/// Owned version of `CssBorderRadiusParseError`.
86#[derive(Debug, Clone, PartialEq, Eq)]
87#[repr(C, u8)]
88pub enum CssBorderRadiusParseErrorOwned {
89    TooManyValues(AzString),
90    PixelValue(CssPixelValueParseErrorOwned),
91}
92
93/// Newtype wrapper around `CssBorderRadiusParseErrorOwned` for the `border-radius` shorthand.
94#[derive(Debug, Clone, PartialEq, Eq)]
95#[repr(C)]
96pub struct CssStyleBorderRadiusParseErrorOwned {
97    pub inner: CssBorderRadiusParseErrorOwned,
98}
99
100impl From<CssBorderRadiusParseErrorOwned> for CssStyleBorderRadiusParseErrorOwned {
101    fn from(v: CssBorderRadiusParseErrorOwned) -> Self {
102        Self { inner: v }
103    }
104}
105
106impl CssBorderRadiusParseError<'_> {
107    #[must_use] pub fn to_contained(&self) -> CssBorderRadiusParseErrorOwned {
108        match self {
109            CssBorderRadiusParseError::TooManyValues(s) => {
110                CssBorderRadiusParseErrorOwned::TooManyValues((*s).to_string().into())
111            }
112            CssBorderRadiusParseError::PixelValue(e) => {
113                CssBorderRadiusParseErrorOwned::PixelValue(e.to_contained())
114            }
115        }
116    }
117}
118
119impl CssBorderRadiusParseErrorOwned {
120    #[must_use] pub fn to_shared(&self) -> CssBorderRadiusParseError<'_> {
121        match self {
122            Self::TooManyValues(s) => {
123                CssBorderRadiusParseError::TooManyValues(s)
124            }
125            Self::PixelValue(e) => {
126                CssBorderRadiusParseError::PixelValue(e.to_shared())
127            }
128        }
129    }
130}
131
132/// Macro to generate error types for individual radius properties.
133macro_rules! define_border_radius_parse_error {
134    ($error_name:ident, $error_name_owned:ident) => {
135        #[derive(Clone, PartialEq, Eq)]
136        pub enum $error_name<'a> {
137            PixelValue(CssPixelValueParseError<'a>),
138        }
139
140        impl_debug_as_display!($error_name<'a>);
141        impl_display! { $error_name<'a>, {
142            PixelValue(e) => format!("{}", e),
143        }}
144
145        impl_from!(CssPixelValueParseError<'a>, $error_name::PixelValue);
146
147        #[derive(Debug, Clone, PartialEq, Eq)]
148        #[repr(C, u8)]
149        pub enum $error_name_owned {
150            PixelValue(CssPixelValueParseErrorOwned),
151        }
152
153        impl $error_name<'_> {
154            #[must_use] pub fn to_contained(&self) -> $error_name_owned {
155                match self {
156                    $error_name::PixelValue(e) => $error_name_owned::PixelValue(e.to_contained()),
157                }
158            }
159        }
160
161        impl $error_name_owned {
162            #[must_use] pub fn to_shared(&self) -> $error_name<'_> {
163                match self {
164                    $error_name_owned::PixelValue(e) => $error_name::PixelValue(e.to_shared()),
165                }
166            }
167        }
168    };
169}
170
171define_border_radius_parse_error!(
172    StyleBorderTopLeftRadiusParseError,
173    StyleBorderTopLeftRadiusParseErrorOwned
174);
175define_border_radius_parse_error!(
176    StyleBorderTopRightRadiusParseError,
177    StyleBorderTopRightRadiusParseErrorOwned
178);
179define_border_radius_parse_error!(
180    StyleBorderBottomLeftRadiusParseError,
181    StyleBorderBottomLeftRadiusParseErrorOwned
182);
183define_border_radius_parse_error!(
184    StyleBorderBottomRightRadiusParseError,
185    StyleBorderBottomRightRadiusParseErrorOwned
186);
187
188// --- Parsing Functions ---
189
190/// Parse the CSS `border-radius` shorthand into individual corner values.
191#[cfg(feature = "parser")]
192/// # Errors
193///
194/// Returns an error if `input` is not a valid CSS `border-radius` value.
195pub fn parse_style_border_radius(
196    input: &str,
197) -> Result<StyleBorderRadius, CssBorderRadiusParseError<'_>> {
198    let components: Vec<_> = input.split_whitespace().collect();
199    let mut values = Vec::with_capacity(components.len());
200    for comp in &components {
201        values.push(parse_pixel_value(comp)?);
202    }
203
204    match values.len() {
205        1 => Ok(StyleBorderRadius {
206            top_left: values[0],
207            top_right: values[0],
208            bottom_right: values[0],
209            bottom_left: values[0],
210        }),
211        2 => Ok(StyleBorderRadius {
212            top_left: values[0],
213            top_right: values[1],
214            bottom_right: values[0],
215            bottom_left: values[1],
216        }),
217        3 => Ok(StyleBorderRadius {
218            top_left: values[0],
219            top_right: values[1],
220            bottom_right: values[2],
221            bottom_left: values[1],
222        }),
223        4 => Ok(StyleBorderRadius {
224            top_left: values[0],
225            top_right: values[1],
226            bottom_right: values[2],
227            bottom_left: values[3],
228        }),
229        _ => Err(CssBorderRadiusParseError::TooManyValues(input)),
230    }
231}
232
233/// Parse the CSS `border-top-left-radius` longhand property.
234#[cfg(feature = "parser")]
235/// # Errors
236///
237/// Returns an error if `input` is not a valid CSS `border-top-left-radius` value.
238pub fn parse_style_border_top_left_radius(
239    input: &str,
240) -> Result<StyleBorderTopLeftRadius, StyleBorderTopLeftRadiusParseError<'_>> {
241    let pixel_value = parse_pixel_value(input)?;
242    Ok(StyleBorderTopLeftRadius { inner: pixel_value })
243}
244
245/// Parse the CSS `border-top-right-radius` longhand property.
246#[cfg(feature = "parser")]
247/// # Errors
248///
249/// Returns an error if `input` is not a valid CSS `border-top-right-radius` value.
250pub fn parse_style_border_top_right_radius(
251    input: &str,
252) -> Result<StyleBorderTopRightRadius, StyleBorderTopRightRadiusParseError<'_>> {
253    let pixel_value = parse_pixel_value(input)?;
254    Ok(StyleBorderTopRightRadius { inner: pixel_value })
255}
256
257/// Parse the CSS `border-bottom-left-radius` longhand property.
258#[cfg(feature = "parser")]
259/// # Errors
260///
261/// Returns an error if `input` is not a valid CSS `border-bottom-left-radius` value.
262pub fn parse_style_border_bottom_left_radius(
263    input: &str,
264) -> Result<StyleBorderBottomLeftRadius, StyleBorderBottomLeftRadiusParseError<'_>> {
265    let pixel_value = parse_pixel_value(input)?;
266    Ok(StyleBorderBottomLeftRadius { inner: pixel_value })
267}
268
269/// Parse the CSS `border-bottom-right-radius` longhand property.
270#[cfg(feature = "parser")]
271/// # Errors
272///
273/// Returns an error if `input` is not a valid CSS `border-bottom-right-radius` value.
274pub fn parse_style_border_bottom_right_radius(
275    input: &str,
276) -> Result<StyleBorderBottomRightRadius, StyleBorderBottomRightRadiusParseError<'_>> {
277    let pixel_value = parse_pixel_value(input)?;
278    Ok(StyleBorderBottomRightRadius { inner: pixel_value })
279}
280
281#[cfg(all(test, feature = "parser"))]
282mod tests {
283    use super::*;
284
285    #[test]
286    fn test_parse_border_radius_shorthand() {
287        // One value
288        let result = parse_style_border_radius("10px").unwrap();
289        assert_eq!(result.top_left, PixelValue::px(10.0));
290        assert_eq!(result.top_right, PixelValue::px(10.0));
291        assert_eq!(result.bottom_right, PixelValue::px(10.0));
292        assert_eq!(result.bottom_left, PixelValue::px(10.0));
293
294        // Two values
295        let result = parse_style_border_radius("10px 5%").unwrap();
296        assert_eq!(result.top_left, PixelValue::px(10.0));
297        assert_eq!(result.top_right, PixelValue::percent(5.0));
298        assert_eq!(result.bottom_right, PixelValue::px(10.0));
299        assert_eq!(result.bottom_left, PixelValue::percent(5.0));
300
301        // Three values
302        let result = parse_style_border_radius("2px 4px 8px").unwrap();
303        assert_eq!(result.top_left, PixelValue::px(2.0));
304        assert_eq!(result.top_right, PixelValue::px(4.0));
305        assert_eq!(result.bottom_right, PixelValue::px(8.0));
306        assert_eq!(result.bottom_left, PixelValue::px(4.0));
307
308        // Four values
309        let result = parse_style_border_radius("1px 0 3px 4px").unwrap();
310        assert_eq!(result.top_left, PixelValue::px(1.0));
311        assert_eq!(result.top_right, PixelValue::px(0.0));
312        assert_eq!(result.bottom_right, PixelValue::px(3.0));
313        assert_eq!(result.bottom_left, PixelValue::px(4.0));
314
315        // Weird whitespace
316        let result = parse_style_border_radius("  1em   2em  ").unwrap();
317        assert_eq!(result.top_left, PixelValue::em(1.0));
318        assert_eq!(result.top_right, PixelValue::em(2.0));
319    }
320
321    #[test]
322    fn test_parse_border_radius_shorthand_errors() {
323        assert!(parse_style_border_radius("").is_err());
324        assert!(parse_style_border_radius("1px 2px 3px 4px 5px").is_err());
325        assert!(parse_style_border_radius("1px bad 3px").is_err());
326    }
327
328    #[test]
329    fn test_parse_longhand_radius() {
330        let result = parse_style_border_top_left_radius("25%").unwrap();
331        assert_eq!(result.inner, PixelValue::percent(25.0));
332    }
333}