embedded_graphics/primitives/arc/mod.rs
1//! The arc primitive
2
3use crate::{
4 geometry::{Angle, Dimensions, Point, Size},
5 primitives::{Circle, PointsIter, Primitive, Rectangle},
6 transform::Transform,
7};
8
9mod points;
10mod styled;
11
12pub use points::Points;
13pub use styled::StyledPixelsIterator;
14
15/// Arc primitive
16///
17/// # Examples
18///
19/// ## Create some arcs with different styles
20///
21/// ```rust
22/// use embedded_graphics::{
23/// pixelcolor::Rgb565,
24/// prelude::*,
25/// primitives::{Arc, PrimitiveStyle, PrimitiveStyleBuilder},
26/// };
27/// # use embedded_graphics::mock_display::MockDisplay;
28/// # let mut display = MockDisplay::default();
29///
30/// // Arc with 1 pixel wide white stroke with top-left point at (10, 20) with a diameter of 30
31/// Arc::new(Point::new(10, 20), 30, 0.0.deg(), 90.0.deg())
32/// .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1))
33/// .draw(&mut display)?;
34///
35/// // Arc with styled stroke with top-left point at (15, 25) with a diameter of 20
36/// let style = PrimitiveStyleBuilder::new()
37/// .stroke_color(Rgb565::RED)
38/// .stroke_width(3)
39/// .build();
40///
41/// Arc::new(Point::new(15, 25), 20, 180.0.deg(), -90.0.deg())
42/// .into_styled(style)
43/// .draw(&mut display)?;
44/// # Ok::<(), core::convert::Infallible>(())
45/// ```
46#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
47#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
48pub struct Arc {
49 /// Top-left point of the bounding-box of the circle supporting the arc
50 pub top_left: Point,
51
52 /// Diameter of the circle supporting the arc
53 pub diameter: u32,
54
55 /// Angle at which the arc starts
56 pub angle_start: Angle,
57
58 /// Angle defining the arc sweep starting at angle_start
59 pub angle_sweep: Angle,
60}
61
62impl Arc {
63 /// Create a new arc delimited with a top-left point with a specific diameter and start and sweep angles
64 pub const fn new(
65 top_left: Point,
66 diameter: u32,
67 angle_start: Angle,
68 angle_sweep: Angle,
69 ) -> Self {
70 Arc {
71 top_left,
72 diameter,
73 angle_start,
74 angle_sweep,
75 }
76 }
77
78 /// Create a new arc centered around a given point with a specific diameter and start and sweep angles
79 pub const fn with_center(
80 center: Point,
81 diameter: u32,
82 angle_start: Angle,
83 angle_sweep: Angle,
84 ) -> Self {
85 Self::from_circle(
86 Circle::with_center(center, diameter),
87 angle_start,
88 angle_sweep,
89 )
90 }
91
92 /// Creates an arc based on a circle.
93 ///
94 /// The resulting arc will match the `top_left` and `diameter` of the base circle.
95 pub const fn from_circle(circle: Circle, angle_start: Angle, angle_sweep: Angle) -> Self {
96 Self {
97 top_left: circle.top_left,
98 diameter: circle.diameter,
99 angle_start,
100 angle_sweep,
101 }
102 }
103
104 /// Returns a circle with the same `top_left` and `diameter` as this arc.
105 pub const fn to_circle(&self) -> Circle {
106 Circle::new(self.top_left, self.diameter)
107 }
108
109 /// Return the center point of the arc.
110 pub fn center(&self) -> Point {
111 self.bounding_box().center()
112 }
113}
114
115impl Primitive for Arc {}
116
117impl PointsIter for Arc {
118 type Iter = Points;
119
120 fn points(&self) -> Self::Iter {
121 Points::new(self)
122 }
123}
124
125impl Dimensions for Arc {
126 fn bounding_box(&self) -> Rectangle {
127 Rectangle::new(self.top_left, Size::new(self.diameter, self.diameter))
128 }
129}
130
131impl Transform for Arc {
132 /// Translate the arc from its current position to a new position by (x, y) pixels,
133 /// returning a new `Arc`. For a mutating transform, see `translate_mut`.
134 ///
135 /// ```
136 /// # use embedded_graphics::primitives::Arc;
137 /// # use embedded_graphics::prelude::*;
138 /// let arc = Arc::new(Point::new(5, 10), 10, 0.0.deg(), 90.0.deg());
139 /// let moved = arc.translate(Point::new(10, 10));
140 ///
141 /// assert_eq!(moved.top_left, Point::new(15, 20));
142 /// ```
143 fn translate(&self, by: Point) -> Self {
144 Self {
145 top_left: self.top_left + by,
146 ..*self
147 }
148 }
149
150 /// Translate the arc from its current position to a new position by (x, y) pixels.
151 ///
152 /// ```
153 /// # use embedded_graphics::primitives::Arc;
154 /// # use embedded_graphics::prelude::*;
155 /// let mut arc = Arc::new(Point::new(5, 10), 10, 0.0.deg(), 90.0.deg());
156 /// arc.translate_mut(Point::new(10, 10));
157 ///
158 /// assert_eq!(arc.top_left, Point::new(15, 20));
159 /// ```
160 fn translate_mut(&mut self, by: Point) -> &mut Self {
161 self.top_left += by;
162
163 self
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use crate::geometry::AngleUnit;
171
172 #[test]
173 fn negative_dimensions() {
174 let arc = Arc::new(Point::new(-15, -15), 10, 0.0.deg(), 90.0.deg());
175
176 assert_eq!(
177 arc.bounding_box(),
178 Rectangle::new(Point::new(-15, -15), Size::new(10, 10))
179 );
180 }
181
182 #[test]
183 fn dimensions() {
184 let arc = Arc::new(Point::new(5, 15), 10, 0.0.deg(), 90.0.deg());
185
186 assert_eq!(
187 arc.bounding_box(),
188 Rectangle::new(Point::new(5, 15), Size::new(10, 10))
189 );
190 }
191
192 #[test]
193 fn it_handles_negative_coordinates() {
194 let positive = Arc::new(Point::new(10, 10), 5, 0.0.deg(), 90.0.deg()).points();
195
196 let negative = Arc::new(Point::new(-10, -10), 5, 0.0.deg(), 90.0.deg()).points();
197
198 assert!(negative.eq(positive.map(|p| p - Point::new(20, 20))));
199 }
200
201 #[test]
202 fn center_is_correct() {
203 // odd diameter
204 let arc = Arc::new(Point::new(10, 10), 5, 0.0.deg(), 90.0.deg());
205 assert_eq!(arc.center(), Point::new(12, 12));
206
207 // even diameter
208 let arc = Arc::new(Point::new(10, 10), 6, 0.0.deg(), 90.0.deg());
209 assert_eq!(arc.center(), Point::new(12, 12));
210
211 // odd diameter
212 let arc = Arc::with_center(Point::new(10, 10), 5, 0.0.deg(), 90.0.deg());
213 assert_eq!(arc.center(), Point::new(10, 10));
214
215 // even diameter
216 let arc = Arc::with_center(Point::new(10, 10), 6, 0.0.deg(), 90.0.deg());
217 assert_eq!(arc.center(), Point::new(10, 10));
218 }
219}