Skip to main content

azul_css/props/style/
border_radius.rs

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