Skip to main content

ferroui_base/media/
rectangle_geometry.rs

1use crate::media::{Geometry, GeometryImpl, GeometryBuilder};
2use crate::platform::{self, IGeometryImpl};
3use crate::{
4    ferro_class, ferro_property, instantiate, FerroObjectImpl, FerroProperty, Rect, Ref,
5    StyledProperty,
6};
7use std::sync::Arc;
8
9/// Represents the geometry of a rectangle.
10#[repr(C)]
11pub struct RectangleGeometry {
12    base: Geometry,
13}
14
15ferro_class!(RectangleGeometry: Geometry);
16crate::ferro_class_info!(RectangleGeometry { new: RectangleGeometry::new });
17
18crate::ferro_impl_classes!(RectangleGeometry: FerroObjectImpl);
19
20impl GeometryImpl for RectangleGeometry {
21    fn clone_geometry(this: &Self) -> Ref<Geometry> {
22        RectangleGeometry::with_rect_and_radii(this.rect(), this.radius_x(), this.radius_y()).upcast()
23    }
24
25    fn create_defining_geometry(this: &Self) -> Option<Arc<dyn IGeometryImpl>> {
26        let radius_x = this.radius_x();
27        let radius_y = this.radius_y();
28        let factory = platform::render_interface();
29
30        if radius_x == 0.0 && radius_y == 0.0 {
31            // Optimization when there are no corner radii
32            Some(factory.create_rectangle_geometry(this.rect()))
33        } else {
34            let mut builder = factory.create_stream_geometry_builder(None);
35            GeometryBuilder::draw_rounded_corners_rectangle(&mut *builder, this.rect(), radius_x, radius_y);
36            let geometry = builder.to_geometry();
37            builder.dispose();
38            Some(geometry)
39        }
40    }
41}
42
43crate::ferro_properties! { impl RectangleGeometry {
44    ferro_property!(pub fn radius_x_property() -> StyledProperty<f64> {
45        FerroProperty::register::<RectangleGeometry, _>("RadiusX", 0.0)
46    });
47
48    ferro_property!(pub fn radius_y_property() -> StyledProperty<f64> {
49        FerroProperty::register::<RectangleGeometry, _>("RadiusY", 0.0)
50    });
51
52    ferro_property!(pub fn rect_property() -> StyledProperty<Rect> {
53        FerroProperty::register::<RectangleGeometry, _>("Rect", Rect::default())
54    });
55} }
56
57impl RectangleGeometry {
58    fn static_constructor() {
59        Geometry::affects_geometry(&[
60            Self::radius_x_property().as_property(),
61            Self::radius_y_property().as_property(),
62            Self::rect_property().as_property(),
63        ]);
64    }
65
66    /// Creates the class data.
67    pub fn construct() -> Self {
68        Self { base: Geometry::construct() }
69    }
70
71    pub fn new() -> Ref<Self> {
72        instantiate(Self::construct())
73    }
74
75    /// Creates a rectangle geometry with the given bounds.
76    pub fn with_rect(rect: Rect) -> Ref<Self> {
77        let result = Self::new();
78        result.set_rect(rect);
79        result
80    }
81
82    /// Creates a rectangle geometry with the given bounds and corner radii.
83    pub fn with_rect_and_radii(rect: Rect, radius_x: f64, radius_y: f64) -> Ref<Self> {
84        let result = Self::new();
85        result.set_rect(rect);
86        result.set_radius_x(radius_x);
87        result.set_radius_y(radius_y);
88        result
89    }
90
91    /// The x-axis radius of the ellipse that is used to round the corners of the rectangle. Corner radii are represented by an ellipse so this is the horizontal axis width of the ellipse.
92    pub fn radius_x(&self) -> f64 {
93        self.get_value(Self::radius_x_property())
94    }
95
96    pub fn set_radius_x(&self, value: f64) {
97        self.set_value(Self::radius_x_property(), value)
98    }
99
100    /// The y-axis radius of the ellipse that is used to round the corners of the rectangle. Corner radii are represented by an ellipse so this is the vertical axis height of the ellipse.
101    pub fn radius_y(&self) -> f64 {
102        self.get_value(Self::radius_y_property())
103    }
104
105    pub fn set_radius_y(&self, value: f64) {
106        self.set_value(Self::radius_y_property(), value)
107    }
108
109    /// The bounds of the rectangle.
110    pub fn rect(&self) -> Rect {
111        self.get_value(Self::rect_property())
112    }
113
114    pub fn set_rect(&self, value: Rect) {
115        self.set_value(Self::rect_property(), value)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::media::geometry::tests::with_mock_render_interface;
123    use crate::media::RotateTransform;
124
125    #[test]
126    fn rectangle_with_transform_can_be_changed() {
127        with_mock_render_interface(|_| {
128            let target = RectangleGeometry::new();
129            target.set_rect(Rect::new(0.0, 0.0, 100.0, 100.0));
130            target.set_transform(RotateTransform::with_angle(45.0));
131
132            target.set_rect(Rect::new(50.0, 50.0, 150.0, 150.0));
133
134            assert!(target.platform_impl().unwrap().as_transformed_geometry().is_some());
135        });
136    }
137
138    #[test]
139    fn rounded_rectangle_is_drawn_into_a_stream_geometry() {
140        with_mock_render_interface(|factory| {
141            let target = RectangleGeometry::with_rect_and_radii(Rect::new(0.0, 0.0, 100.0, 50.0), 10.0, 5.0);
142            assert!(target.platform_impl().is_some());
143            assert_eq!(vec!["stream"], *factory.created.borrow());
144            assert_eq!(
145                vec![
146                    "begin 10, 0 true",
147                    "line 90, 0 true",
148                    "arc 100, 5 10, 5 0 false Clockwise true",
149                    "line 100, 45 true",
150                    "arc 90, 50 10, 5 0 false Clockwise true",
151                    "line 10, 50 true",
152                    "arc 0, 45 10, 5 0 false Clockwise true",
153                    "line 0, 5 true",
154                    "arc 10, 0 10, 5 0 false Clockwise true",
155                    "end true",
156                    "dispose",
157                ],
158                *factory.streams.borrow()[0].lock().unwrap()
159            );
160
161            let clone = target.clone_geometry().cast::<RectangleGeometry>().unwrap();
162            assert_eq!((Rect::new(0.0, 0.0, 100.0, 50.0), 10.0, 5.0), (clone.rect(), clone.radius_x(), clone.radius_y()));
163
164            target.set_radius_x(0.0);
165            target.set_radius_y(0.0);
166            assert!(target.platform_impl().is_some());
167            assert_eq!(vec!["stream", "rectangle 0, 0, 100, 50"], *factory.created.borrow());
168        });
169    }
170}