embedded_graphics/primitives/polyline/
mod.rs

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}