buffer_graphics_lib/shapes/polyline/
mod.rs

1pub mod error;
2pub mod rendering;
3
4use crate::shapes::polyline::error::PolylineError;
5use crate::shapes::polyline::error::PolylineError::{InvalidPolyline, PolylineAlreadyClosed};
6use crate::shapes::polyline::Segment::*;
7use graphics_shapes::coord::Coord;
8use ici_files::prelude::*;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
14pub enum Segment {
15    Start(Coord),
16    LineTo(Coord),
17    ArcAround {
18        center: Coord,
19        angle_start: isize,
20        angle_end: isize,
21        radius: usize,
22    },
23}
24
25impl Segment {
26    fn end_coord(&self) -> Coord {
27        match self {
28            Start(c) => *c,
29            LineTo(c) => *c,
30            ArcAround {
31                center,
32                radius,
33                angle_end,
34                ..
35            } => Coord::from_angle(center, *radius, *angle_end),
36        }
37    }
38}
39
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Polyline {
43    segments: Vec<Segment>,
44    color: Color,
45    closed: bool,
46}
47
48impl Polyline {
49    pub fn new(segments: Vec<Segment>, color: Color) -> Self {
50        Self {
51            segments,
52            color,
53            closed: false,
54        }
55    }
56
57    pub fn start<P: Into<Coord>>(start_at: P, color: Color) -> Self {
58        Self {
59            segments: vec![Start(start_at.into())],
60            color,
61            closed: false,
62        }
63    }
64
65    pub fn rounded_rect(
66        left: isize,
67        top: isize,
68        right: isize,
69        bottom: isize,
70        corner_size: usize,
71        color: Color,
72    ) -> Result<Self, PolylineError> {
73        let corner_size = corner_size as isize;
74        let tl_arc = Coord::from((left + corner_size, top + corner_size));
75        let tr_arc = Coord::from((right - corner_size, top + corner_size));
76        let bl_arc = Coord::from((left + corner_size, bottom - corner_size));
77        let br_arc = Coord::from((right - corner_size, bottom - corner_size));
78
79        let line1_end = Coord::from((right - corner_size, top));
80        let line2_end = Coord::from((right, bottom - corner_size));
81        let line3_end = Coord::from((left + corner_size, bottom));
82        let line4_end = Coord::from((left, top + corner_size));
83
84        Polyline::start((left + corner_size, top), color)
85            .add_line_to(line1_end)?
86            .add_arc_around(tr_arc, corner_size as usize, 0, 90)?
87            .add_line_to(line2_end)?
88            .add_arc_around(br_arc, corner_size as usize, 90, 90)?
89            .add_line_to(line3_end)?
90            .add_arc_around(bl_arc, corner_size as usize, 180, 90)?
91            .add_line_to(line4_end)?
92            .add_arc_around(tl_arc, corner_size as usize, 270, 90)
93    }
94}
95
96impl Polyline {
97    pub fn with_color(&self, color: Color) -> Self {
98        let mut cloned = self.clone();
99        cloned.color = color;
100        cloned
101    }
102}
103
104impl Polyline {
105    pub fn add_line_to<P: Into<Coord>>(mut self, point: P) -> Result<Self, PolylineError> {
106        if self.closed {
107            return Err(PolylineAlreadyClosed);
108        }
109        self.segments.push(LineTo(point.into()));
110        Ok(self)
111    }
112
113    pub fn add_arc_around<P: Into<Coord>>(
114        mut self,
115        center: P,
116        radius: usize,
117        start_angle: isize,
118        sweep_angle: usize,
119    ) -> Result<Self, PolylineError> {
120        if self.closed {
121            return Err(PolylineAlreadyClosed);
122        }
123        self.segments.push(ArcAround {
124            center: center.into(),
125            radius,
126            angle_start: start_angle,
127            angle_end: start_angle + (sweep_angle as isize),
128        });
129        Ok(self)
130    }
131
132    pub fn close(self) -> Result<Self, PolylineError> {
133        if let Start(coord) = self.segments[0] {
134            let mut tmp = self.add_line_to(coord)?;
135            tmp.closed = true;
136            Ok(tmp)
137        } else {
138            Err(InvalidPolyline)
139        }
140    }
141}
142
143#[cfg(test)]
144mod test {
145    use crate::shapes::polyline::Polyline;
146    use crate::shapes::polyline::Segment::*;
147    use graphics_shapes::coord::Coord;
148    use ici_files::prelude::RED;
149
150    #[test]
151    fn check_closing() {
152        let polyline = Polyline::start((10, 10), RED)
153            .add_line_to((20, 10))
154            .unwrap()
155            .add_line_to((20, 20))
156            .unwrap()
157            .add_line_to((10, 20))
158            .unwrap()
159            .close()
160            .unwrap();
161        assert_eq!(
162            polyline.segments,
163            vec![
164                Start(Coord::new(10, 10)),
165                LineTo(Coord::new(20, 10)),
166                LineTo(Coord::new(20, 20)),
167                LineTo(Coord::new(10, 20)),
168                LineTo(Coord::new(10, 10)),
169            ]
170        )
171    }
172}