ferroui_controls/shapes/
arc.rs1use 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#[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 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 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 pub fn start_angle_property() -> StyledProperty<f64> {
93 FerroProperty::register::<Arc, _>("StartAngle", 0.0)
94 }
95 );
96
97 ferro_property!(
98 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 pub fn construct() -> Self {
118 Self { base: Shape::construct() }
119 }
120
121 pub fn new() -> Ref<Self> {
122 instantiate(Self::construct())
123 }
124
125 pub fn start_angle(&self) -> f64 {
127 self.get_value(Self::start_angle_property())
128 }
129
130 pub fn set_start_angle(&self, value: f64) {
132 self.set_value(Self::start_angle_property(), value)
133 }
134
135 pub fn sweep_angle(&self) -> f64 {
139 self.get_value(Self::sweep_angle_property())
140 }
141
142 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}