Skip to main content

ferroui_controls/shapes/
arc.rs

1use super::{Shape, ShapeImpl};
2use crate::ControlImpl;
3use ferroui_base::input::InputElementImpl;
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::LayoutableImpl;
6use ferroui_base::media::{EllipseGeometry, Geometry, StreamGeometry, SweepDirection};
7use ferroui_base::platform::IGeometryContext;
8use ferroui_base::utilities::MathUtilities;
9use ferroui_base::{
10    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroProperty,
11    Point, Rect, Ref, Size, StyledElementImpl, StyledProperty, Visual, VisualImpl,
12};
13use std::f64::consts::PI;
14
15/// Represents a circular or elliptical arc (a segment of a curve).
16#[repr(C)]
17pub struct Arc {
18    base: Shape,
19}
20
21ferro_class!(Arc: Shape);
22ferroui_base::ferro_class_info!(Arc { new: Arc::new });
23ferro_impl_classes!(
24    Arc: StyledElementImpl,
25    VisualImpl,
26    LayoutableImpl,
27    InteractiveImpl,
28    InputElementImpl,
29    ControlImpl
30);
31
32ferroui_base::ferro_impl_classes!(Arc: FerroObjectImpl);
33
34impl ShapeImpl for Arc {
35    fn create_defining_geometry(this: &Self) -> Option<Ref<Geometry>> {
36        let angle1 = MathUtilities::deg2rad(this.start_angle());
37        let angle2 = angle1 + MathUtilities::deg2rad(this.sweep_angle());
38
39        let start_angle = f64::min(angle1, angle2);
40        let sweep_angle = f64::max(angle1, angle2);
41
42        let norm_start = Self::rad_to_norm_rad(start_angle);
43        let norm_end = Self::rad_to_norm_rad(sweep_angle);
44
45        let rect = Rect::from_size(this.bounds().size());
46
47        if norm_start == norm_end && start_angle != sweep_angle {
48            // Complete ring.
49            Some(EllipseGeometry::with_rect(rect.deflate(this.stroke_thickness() / 2.0)).upcast())
50        } else if this.sweep_angle() == 0.0 {
51            Some(StreamGeometry::new().upcast())
52        } else {
53            // Partial arc.
54            let deflated_rect = rect.deflate(this.stroke_thickness() / 2.0);
55
56            let center_x = rect.center().x;
57            let center_y = rect.center().y;
58
59            let radius_x = deflated_rect.width / 2.0;
60            let radius_y = deflated_rect.height / 2.0;
61
62            let angle_gap = Self::rad_to_norm_rad(sweep_angle - start_angle);
63
64            let start_point = Self::get_ring_point(radius_x, radius_y, center_x, center_y, start_angle);
65            let end_point = Self::get_ring_point(radius_x, radius_y, center_x, center_y, sweep_angle);
66
67            let arc_geometry = StreamGeometry::new();
68
69            {
70                let mut context = arc_geometry.open();
71                context.begin_figure(start_point, false);
72                context.arc_to(
73                    end_point,
74                    Size::new(radius_x, radius_y),
75                    0.0,
76                    angle_gap >= PI,
77                    SweepDirection::Clockwise,
78                    true,
79                );
80                context.end_figure(false);
81                context.dispose();
82            }
83
84            Some(arc_geometry.upcast())
85        }
86    }
87}
88
89ferroui_base::ferro_properties! { impl Arc {
90    ferro_property!(
91        /// Defines the `StartAngle` property.
92        pub fn start_angle_property() -> StyledProperty<f64> {
93            FerroProperty::register::<Arc, _>("StartAngle", 0.0)
94        }
95    );
96
97    ferro_property!(
98        /// Defines the `SweepAngle` property.
99        pub fn sweep_angle_property() -> StyledProperty<f64> {
100            FerroProperty::register::<Arc, _>("SweepAngle", 0.0)
101        }
102    );
103} }
104
105impl Arc {
106    fn static_constructor() {
107        Shape::stroke_thickness_property().override_default_value::<Arc>(1.0);
108        Shape::affects_geometry::<Arc>(&[
109            Visual::bounds_property().as_property(),
110            Shape::stroke_thickness_property().as_property(),
111            Self::start_angle_property().as_property(),
112            Self::sweep_angle_property().as_property(),
113        ]);
114    }
115
116    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
117    pub fn construct() -> Self {
118        Self { base: Shape::construct() }
119    }
120
121    pub fn new() -> Ref<Self> {
122        instantiate(Self::construct())
123    }
124
125    /// Gets the angle at which the arc starts, in degrees.
126    pub fn start_angle(&self) -> f64 {
127        self.get_value(Self::start_angle_property())
128    }
129
130    /// Sets the angle at which the arc starts, in degrees.
131    pub fn set_start_angle(&self, value: f64) {
132        self.set_value(Self::start_angle_property(), value)
133    }
134
135    /// Gets the angle, in degrees, added to the `StartAngle` defining where
136    /// the arc ends. A positive value is clockwise, negative is
137    /// counter-clockwise.
138    pub fn sweep_angle(&self) -> f64 {
139        self.get_value(Self::sweep_angle_property())
140    }
141
142    /// Sets the angle, in degrees, added to the `StartAngle` defining where
143    /// the arc ends. A positive value is clockwise, negative is
144    /// counter-clockwise.
145    pub fn set_sweep_angle(&self, value: f64) {
146        self.set_value(Self::sweep_angle_property(), value)
147    }
148
149    fn rad_to_norm_rad(in_angle: f64) -> f64 {
150        ((in_angle % (PI * 2.0)) + (PI * 2.0)) % (PI * 2.0)
151    }
152
153    fn get_ring_point(radius_x: f64, radius_y: f64, center_x: f64, center_y: f64, angle: f64) -> Point {
154        Point::new((radius_x * angle.cos()) + center_x, (radius_y * angle.sin()) + center_y)
155    }
156}