embedded_graphics/primitives/sector/
mod.rs1use crate::{
4 geometry::{Angle, Dimensions, Point, Size},
5 primitives::{
6 common::PlaneSector, Circle, ContainsPoint, OffsetOutline, PointsIter, Primitive, Rectangle,
7 },
8 transform::Transform,
9};
10
11mod points;
12mod styled;
13
14pub use points::Points;
15pub use styled::StyledPixelsIterator;
16
17#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
57#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
58pub struct Sector {
59 pub top_left: Point,
61
62 pub diameter: u32,
64
65 pub angle_start: Angle,
67
68 pub angle_sweep: Angle,
70}
71
72impl Sector {
73 pub const fn new(
75 top_left: Point,
76 diameter: u32,
77 angle_start: Angle,
78 angle_sweep: Angle,
79 ) -> Self {
80 Sector {
81 top_left,
82 diameter,
83 angle_start,
84 angle_sweep,
85 }
86 }
87
88 pub const fn with_center(
90 center: Point,
91 diameter: u32,
92 angle_start: Angle,
93 angle_sweep: Angle,
94 ) -> Self {
95 let top_left = Rectangle::with_center(center, Size::new_equal(diameter)).top_left;
96
97 Sector {
98 top_left,
99 diameter,
100 angle_start,
101 angle_sweep,
102 }
103 }
104
105 pub const fn from_circle(circle: Circle, angle_start: Angle, angle_sweep: Angle) -> Self {
109 Sector {
110 top_left: circle.top_left,
111 diameter: circle.diameter,
112 angle_start,
113 angle_sweep,
114 }
115 }
116
117 pub const fn to_circle(&self) -> Circle {
119 Circle::new(self.top_left, self.diameter)
120 }
121
122 pub fn center(&self) -> Point {
124 self.bounding_box().center()
125 }
126
127 fn center_2x(&self) -> Point {
132 let radius = self.diameter.saturating_sub(1);
134
135 self.top_left * 2 + Size::new(radius, radius)
136 }
137}
138
139impl OffsetOutline for Sector {
140 fn offset(&self, offset: i32) -> Self {
141 let circle = self.to_circle().offset(offset);
142
143 Self::from_circle(circle, self.angle_start, self.angle_sweep)
144 }
145}
146
147impl Primitive for Sector {}
148
149impl PointsIter for Sector {
150 type Iter = Points;
151
152 fn points(&self) -> Self::Iter {
153 Points::new(self)
154 }
155}
156
157impl ContainsPoint for Sector {
158 fn contains(&self, point: Point) -> bool {
159 if self.to_circle().contains(point) {
160 let delta = point * 2 - self.center_2x();
161 PlaneSector::new(self.angle_start, self.angle_sweep).contains(delta)
162 } else {
163 false
164 }
165 }
166}
167
168impl Dimensions for Sector {
169 fn bounding_box(&self) -> Rectangle {
170 Rectangle::new(self.top_left, Size::new_equal(self.diameter))
171 }
172}
173
174impl Transform for Sector {
175 fn translate(&self, by: Point) -> Self {
187 Self {
188 top_left: self.top_left + by,
189 ..*self
190 }
191 }
192
193 fn translate_mut(&mut self, by: Point) -> &mut Self {
204 self.top_left += by;
205
206 self
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use crate::geometry::AngleUnit;
214
215 #[test]
216 fn negative_dimensions() {
217 let sector = Sector::new(Point::new(-15, -15), 10, 0.0.deg(), 90.0.deg());
218
219 assert_eq!(
220 sector.bounding_box(),
221 Rectangle::new(Point::new(-15, -15), Size::new(10, 10))
222 );
223 }
224
225 #[test]
226 fn dimensions() {
227 let sector = Sector::new(Point::new(5, 15), 10, 0.0.deg(), 90.0.deg());
228
229 assert_eq!(
230 sector.bounding_box(),
231 Rectangle::new(Point::new(5, 15), Size::new(10, 10))
232 );
233 }
234
235 #[test]
236 fn it_handles_negative_coordinates() {
237 let positive = Sector::new(Point::new(10, 10), 5, 0.0.deg(), 90.0.deg()).points();
238
239 let negative = Sector::new(Point::new(-10, -10), 5, 0.0.deg(), 90.0.deg()).points();
240
241 assert!(negative.eq(positive.map(|p| p - Point::new(20, 20))));
242 }
243
244 #[test]
245 fn center_is_correct() {
246 let sector = Sector::new(Point::new(10, 10), 5, 0.0.deg(), 90.0.deg());
248 assert_eq!(sector.center(), Point::new(12, 12));
249
250 let sector = Sector::new(Point::new(10, 10), 6, 0.0.deg(), 90.0.deg());
252 assert_eq!(sector.center(), Point::new(12, 12));
253
254 let sector = Sector::with_center(Point::new(10, 10), 5, 0.0.deg(), 90.0.deg());
256 assert_eq!(sector.center(), Point::new(10, 10));
257
258 let sector = Sector::with_center(Point::new(10, 10), 6, 0.0.deg(), 90.0.deg());
260 assert_eq!(sector.center(), Point::new(10, 10));
261 }
262
263 #[test]
264 fn contains() {
265 let sector = Sector::new(Point::zero(), 10, 0.0.deg(), 90.0.deg());
266
267 let contained_points = Rectangle::new(Point::new(-10, -10), Size::new(30, 30))
268 .points()
269 .filter(|p| sector.contains(*p));
270
271 assert!(contained_points.eq(sector.points()));
272 }
273
274 #[test]
275 fn offset() {
276 let center = Point::new(5, 7);
277 let sector = Sector::with_center(center, 3, 0.0.deg(), 90.0.deg());
278
279 assert_eq!(sector.offset(0), sector);
280
281 assert_eq!(
282 sector.offset(1),
283 Sector::with_center(center, 5, 0.0.deg(), 90.0.deg())
284 );
285 assert_eq!(
286 sector.offset(2),
287 Sector::with_center(center, 7, 0.0.deg(), 90.0.deg())
288 );
289
290 assert_eq!(
291 sector.offset(-1),
292 Sector::with_center(center, 1, 0.0.deg(), 90.0.deg())
293 );
294 assert_eq!(
295 sector.offset(-2),
296 Sector::with_center(center, 0, 0.0.deg(), 90.0.deg())
297 );
298 assert_eq!(
299 sector.offset(-3),
300 Sector::with_center(center, 0, 0.0.deg(), 90.0.deg())
301 );
302 }
303}