Skip to main content

ferroui_base/
corner_radius.rs

1//! Represents the radii of a rectangle's corners.
2
3use std::fmt;
4use std::str::FromStr;
5
6use crate::utilities::math_utilities;
7use crate::utilities::span_helpers::InvariantF64;
8use crate::utilities::{FormatError, SpanStringTokenizer};
9
10/// Represents the radii of a rectangle's corners.
11#[derive(Clone, Copy, Debug, PartialEq, Default)]
12pub struct CornerRadius {
13    /// Radius of the top left corner.
14    pub top_left: f64,
15    /// Radius of the top right corner.
16    pub top_right: f64,
17    /// Radius of the bottom right corner.
18    pub bottom_right: f64,
19    /// Radius of the bottom left corner.
20    pub bottom_left: f64,
21}
22
23impl CornerRadius {
24    /// Initializes a [`CornerRadius`] from the four corner radii, clockwise from the top left.
25    #[inline]
26    pub const fn new(top_left: f64, top_right: f64, bottom_right: f64, bottom_left: f64) -> Self {
27        Self {
28            top_left,
29            top_right,
30            bottom_right,
31            bottom_left,
32        }
33    }
34
35    /// Initializes a [`CornerRadius`] with the same radius on every corner.
36    #[inline]
37    pub const fn uniform(uniform_radius: f64) -> Self {
38        Self::new(
39            uniform_radius,
40            uniform_radius,
41            uniform_radius,
42            uniform_radius,
43        )
44    }
45
46    /// Initializes a [`CornerRadius`] from the radius of the top corners and the
47    /// radius of the bottom corners.
48    #[inline]
49    pub const fn top_bottom(top: f64, bottom: f64) -> Self {
50        Self::new(top, top, bottom, bottom)
51    }
52
53    /// Gets a value indicating whether the instance has equal values for all
54    /// corners (NaN compares equal to NaN here).
55    #[inline]
56    pub fn is_uniform(&self) -> bool {
57        math_utilities::equals(self.top_left, self.top_right)
58            && math_utilities::equals(self.bottom_left, self.bottom_right)
59            && math_utilities::equals(self.top_right, self.bottom_right)
60    }
61
62    /// Returns a boolean indicating whether the corner radius is equal to the other
63    /// given corner radius (exact comparison).
64    #[inline]
65    pub fn equals(&self, other: CornerRadius) -> bool {
66        self.top_left == other.top_left
67            && self.top_right == other.top_right
68            && self.bottom_right == other.bottom_right
69            && self.bottom_left == other.bottom_left
70    }
71
72    /// Parses a [`CornerRadius`] string: one, two or four numbers.
73    pub fn parse(s: &str) -> Result<Self, FormatError> {
74        const EXCEPTION_MESSAGE: &str = "Invalid CornerRadius.";
75
76        SpanStringTokenizer::with_message(s, EXCEPTION_MESSAGE).scope(|t| {
77            if let Some(a) = t.try_read_double()? {
78                if let Some(b) = t.try_read_double()? {
79                    if let Some(c) = t.try_read_double()? {
80                        return Ok(CornerRadius::new(a, b, c, t.read_double()?));
81                    }
82
83                    return Ok(CornerRadius::top_bottom(a, b));
84                }
85
86                return Ok(CornerRadius::uniform(a));
87            }
88
89            Err(FormatError::new(EXCEPTION_MESSAGE))
90        })
91    }
92}
93
94impl FromStr for CornerRadius {
95    type Err = FormatError;
96    #[inline]
97    fn from_str(s: &str) -> Result<Self, FormatError> {
98        CornerRadius::parse(s)
99    }
100}
101
102impl fmt::Display for CornerRadius {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(
105            f,
106            "{},{},{},{}",
107            InvariantF64(self.top_left),
108            InvariantF64(self.top_right),
109            InvariantF64(self.bottom_right),
110            InvariantF64(self.bottom_left)
111        )
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn parse_parses_single_uniform_radius() {
121        let result = CornerRadius::parse("3.4").unwrap();
122
123        assert_eq!(CornerRadius::uniform(3.4), result);
124    }
125
126    #[test]
127    fn parse_parses_top_bottom() {
128        let result = CornerRadius::parse("1.1,2.2").unwrap();
129
130        assert_eq!(CornerRadius::top_bottom(1.1, 2.2), result);
131    }
132
133    #[test]
134    fn parse_parses_top_left_top_right_bottom_right_bottom_left() {
135        let result = CornerRadius::parse("1.1,2.2,3.3,4.4").unwrap();
136
137        assert_eq!(CornerRadius::new(1.1, 2.2, 3.3, 4.4), result);
138    }
139
140    #[test]
141    fn parse_accepts_spaces() {
142        let result = CornerRadius::parse("1.1 2.2 3.3 4.4").unwrap();
143
144        assert_eq!(CornerRadius::new(1.1, 2.2, 3.3, 4.4), result);
145    }
146
147    // Additional coverage (not part of the reference suite).
148
149    #[test]
150    fn display_uniform_and_errors() {
151        assert_eq!(
152            CornerRadius::new(1.0, 2.0, 3.0, 4.5).to_string(),
153            "1,2,3,4.5"
154        );
155        assert!(CornerRadius::uniform(3.0).is_uniform());
156        assert!(!CornerRadius::top_bottom(1.0, 2.0).is_uniform());
157        assert_eq!(
158            CornerRadius::parse("a").unwrap_err().message(),
159            "Invalid CornerRadius."
160        );
161        assert!(CornerRadius::parse("1,2,3").is_err());
162    }
163}