1//! The polyline primitive
2
3use crate::{
4 geometry::{Dimensions, Point, Size},
5 primitives::{PointsIter, Primitive, Rectangle},
6 transform::Transform,
7};
8
9mod points;
10pub(in crate::primitives) mod scanline_intersections;
11mod scanline_iterator;
12mod styled;
13
14pub use points::Points;
15pub use styled::StyledPixelsIterator;
16
17/// Polyline primitive
18///
19/// Creates an unfilled chained line shape.
20///
21/// # Examples
22///
23/// ## Draw a "heartbeat" shaped polyline
24///
25/// This example draws a stylized cardiogram in a 5px green stroke.
26///
27/// ```rust
28/// use embedded_graphics::{
29/// pixelcolor::Rgb565, prelude::*, primitives::{Polyline, PrimitiveStyle},
30/// };
31/// # use embedded_graphics::mock_display::MockDisplay;
32/// # let mut display = MockDisplay::default();
33/// # display.set_allow_out_of_bounds_drawing(true);
34///
35/// // A "heartbeat" shaped polyline
36/// let points: [Point; 10] = [
37/// Point::new(10, 64),
38/// Point::new(50, 64),
39/// Point::new(60, 44),
40/// Point::new(70, 64),
41/// Point::new(80, 64),
42/// Point::new(90, 74),
43/// Point::new(100, 10),
44/// Point::new(110, 84),
45/// Point::new(120, 64),
46/// Point::new(300, 64),
47/// ];
48///
49/// let line_style = PrimitiveStyle::with_stroke(Rgb565::GREEN, 5);
50///
51/// Polyline::new(&points)
52/// .into_styled(line_style)
53/// .draw(&mut display)?;
54/// # Ok::<(), core::convert::Infallible>(())
55/// ```
56#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
57#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
58pub struct Polyline<'a> {
59 /// An offset to apply to the polyline as a whole
60 pub translate: Point,
61
62 /// All vertices in the line
63 pub vertices: &'a [Point],
64}
65
66impl<'a> Polyline<'a> {
67 /// Create a new polyline from a list of vertices
68 ///
69 /// If fewer than two vertices are provided, the line will not render anything when drawn.
70 pub const fn new(vertices: &'a [Point]) -> Self {
71 Self {
72 vertices,
73 translate: Point::zero(),
74 }
75 }
76}
77
78impl<'a> Primitive for Polyline<'a> {}
79
80impl<'a> PointsIter for Polyline<'a> {
81 type Iter = Points<'a>;
82
83 fn points(&self) -> Self::Iter {
84 Points::new(self)
85 }
86}
87
88impl<'a> Dimensions for Polyline<'a> {
89 fn bounding_box(&self) -> Rectangle {
90 match self.vertices {
91 [] => Rectangle::zero(),
92 [v] => Rectangle::new(*v, Size::zero()),
93 vertices => {
94 let top_left = vertices
95 .iter()
96 .map(|v| *v + self.translate)
97 .fold(Point::new(core::i32::MAX, core::i32::MAX), |accum, v| {
98 Point::new(accum.x.min(v.x), accum.y.min(v.y))
99 });
100
101 let bottom_right = vertices
102 .iter()
103 .map(|v| *v + self.translate)
104 .fold(Point::new(core::i32::MIN, core::i32::MIN), |accum, v| {
105 Point::new(accum.x.max(v.x), accum.y.max(v.y))
106 });
107
108 Rectangle::with_corners(top_left, bottom_right)
109 }
110 }
111 }
112}
113
114impl<'a> Transform for Polyline<'a> {
115 /// Translate the polyline from its current position to a new position by (x, y) pixels, returning
116 /// a new `Polyline`. For a mutating transform, see `translate_mut`.
117 ///
118 /// ```
119 /// # use embedded_graphics::primitives::Polyline;
120 /// # use embedded_graphics::prelude::*;
121 /// let points = [
122 /// Point::new(5, 10),
123 /// Point::new(7, 7),
124 /// Point::new(5, 8),
125 /// Point::new(10, 10),
126 /// ];
127 ///
128 /// let polyline = Polyline::new(&points);
129 /// let moved = polyline.translate(Point::new(10, 12));
130 ///
131 /// assert_eq!(polyline.bounding_box().top_left, Point::new(5, 7));
132 /// assert_eq!(moved.bounding_box().top_left, Point::new(15, 19));
133 /// ```
134 fn translate(&self, by: Point) -> Self {
135 Self {
136 translate: self.translate + by,
137 ..*self
138 }
139 }
140
141 /// Translate the polyline from its current position to a new position by (x, y) pixels.
142 ///
143 /// ```
144 /// # use embedded_graphics::primitives::Polyline;
145 /// # use embedded_graphics::prelude::*;
146 /// let points = [
147 /// Point::new(5, 10),
148 /// Point::new(7, 7),
149 /// Point::new(5, 8),
150 /// Point::new(10, 10),
151 /// ];
152 ///
153 /// let mut polyline = Polyline::new(&points);
154 ///
155 /// polyline.translate_mut(Point::new(10, 12));
156 ///
157 /// assert_eq!(polyline.bounding_box().top_left, Point::new(15, 19));
158 /// ```
159 fn translate_mut(&mut self, by: Point) -> &mut Self {
160 self.translate += by;
161
162 self
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::geometry::{Point, Size};
170
171 // A "heartbeat" shaped polyline
172 pub(in crate::primitives::polyline) const HEARTBEAT: [Point; 10] = [
173 Point::new(10, 64),
174 Point::new(50, 64),
175 Point::new(60, 44),
176 Point::new(70, 64),
177 Point::new(80, 64),
178 Point::new(90, 74),
179 Point::new(100, 10),
180 Point::new(110, 84),
181 Point::new(120, 64),
182 Point::new(300, 64),
183 ];
184
185 // Smaller test pattern for mock display
186 pub(in crate::primitives::polyline) const SMALL: [Point; 4] = [
187 Point::new(2, 5),
188 Point::new(5, 2),
189 Point::new(10, 5),
190 Point::new(15, 2),
191 ];
192
193 #[test]
194 fn special_case_dimensions() {
195 assert_eq!(Polyline::new(&[]).bounding_box(), Rectangle::zero(),);
196
197 assert_eq!(
198 Polyline::new(&[Point::new(15, 17)]).bounding_box(),
199 Rectangle::new(Point::new(15, 17), Size::zero())
200 );
201 }
202
203 #[test]
204 fn positive_dimensions() {
205 let polyline = Polyline::new(&HEARTBEAT);
206
207 let bb = polyline.bounding_box();
208
209 assert_eq!(
210 bb,
211 Rectangle::with_corners(Point::new(10, 10), Point::new(300, 84))
212 );
213 }
214
215 #[test]
216 fn negative_dimensions() {
217 let mut negative: [Point; 10] = [Point::zero(); 10];
218
219 for (i, v) in HEARTBEAT.iter().enumerate() {
220 negative[i] = *v - Point::new(100, 100);
221 }
222
223 let polyline = Polyline::new(&negative);
224
225 let bb = polyline.bounding_box();
226
227 assert_eq!(
228 bb,
229 Rectangle::with_corners(Point::new(-90, -90), Point::new(200, -16))
230 );
231 }
232
233 #[test]
234 fn transformed_dimensions() {
235 let polyline = Polyline::new(&HEARTBEAT).translate(Point::new(-100, -100));
236
237 let bb = polyline.bounding_box();
238
239 assert_eq!(
240 bb,
241 Rectangle::with_corners(Point::new(-90, -90), Point::new(200, -16))
242 );
243 }
244
245 #[test]
246 fn translate_does_not_modify_size() {
247 let points = [
248 Point::new(5, 10),
249 Point::new(7, 7),
250 Point::new(5, 8),
251 Point::new(10, 10),
252 ];
253
254 let polyline = Polyline::new(&points);
255 let moved = polyline.translate(Point::new(10, 12));
256
257 assert_eq!(moved.bounding_box().size, polyline.bounding_box().size);
258 }
259
260 #[test]
261 fn translate_translated() {
262 let points = [
263 Point::new(5, 10),
264 Point::new(7, 7),
265 Point::new(5, 8),
266 Point::new(10, 10),
267 ];
268
269 let polyline = Polyline::new(&points);
270 let moved = polyline.translate(Point::new(10, 12));
271 let moved2 = moved.translate(Point::new(10, 12));
272
273 assert_eq!(
274 moved.bounding_box(),
275 polyline.bounding_box().translate(Point::new(10, 12))
276 );
277 assert_eq!(
278 moved2.bounding_box(),
279 polyline.bounding_box().translate(Point::new(20, 24))
280 );
281 }
282}
283