plotters_conrod/
backend.rs

1// plotters-conrod
2//
3// Conrod backend for Plotters
4// Copyright: 2020, Valerian Saliou <valerian@valeriansaliou.name>
5// License: MIT
6
7use std::convert::From;
8
9use conrod_core::{self as conrod, position::Scalar as ConrodScalar, Positionable, Widget};
10use plotters_backend::{
11    text_anchor, BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend,
12    DrawingErrorKind,
13};
14
15use crate::error::ConrodBackendError;
16use crate::graph::ConrodBackendReusableGraph;
17use crate::triangulate;
18use crate::utils::{color, convert, path, position, shape};
19
20/// The Conrod drawing backend
21pub struct ConrodBackend<'a, 'b> {
22    ui: &'a mut conrod::UiCell<'b>,
23    size: (u32, u32),
24    parent: conrod::widget::Id,
25    font: conrod::text::font::Id,
26    graph: &'a mut ConrodBackendReusableGraph,
27}
28
29impl<'a, 'b> ConrodBackend<'a, 'b> {
30    /// Create a new Conrod backend drawer, with:
31    /// - `ui`: the `UiCell` that was derived from `Ui` for this frame
32    /// - `(plot_width, plot_height)`: the size of your plot in pixels (make sure it matches its parent canvas size)
33    /// - `ids.parent`: the `widget::Id` of the canvas that contains your plot (of the same size than the plot itself)
34    /// - `fonts.regular`: the `font::Id` of the font to use to draw text (ie. a Conrod font identifier)
35    /// - `conrod_graph`: a mutable reference to the graph instance you built outside of the drawing loop (pass it as a mutable reference)
36    pub fn new(
37        ui: &'a mut conrod::UiCell<'b>,
38        size: (u32, u32),
39        parent: conrod::widget::Id,
40        font: conrod::text::font::Id,
41        graph: &'a mut ConrodBackendReusableGraph,
42    ) -> Self {
43        // Important: prepare the IDs graph, and reset all incremented IDs counters back to zero; \
44        //   if we do not do that, counts will increment forever and the graph will be enlarged \
45        //   infinitely, which would result in a huge memory leak.
46        graph.prepare();
47
48        Self {
49            ui,
50            parent,
51            font,
52            size,
53            graph,
54        }
55    }
56}
57
58impl<'a, 'b> DrawingBackend for ConrodBackend<'a, 'b> {
59    type ErrorType = ConrodBackendError;
60
61    fn get_size(&self) -> (u32, u32) {
62        self.size
63    }
64
65    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<ConrodBackendError>> {
66        Ok(())
67    }
68
69    fn present(&mut self) -> Result<(), DrawingErrorKind<ConrodBackendError>> {
70        Ok(())
71    }
72
73    fn draw_pixel(
74        &mut self,
75        _point: BackendCoord,
76        _color: BackendColor,
77    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
78        // Not supported yet (rendering ignored)
79        // Notice: doing this efficiently would require building an internal buffer on 'self', and \
80        //   rendering it as a Conrod image widget when the final call to 'present()' is done. \
81        //   doing it solely by drawing Conrod rectangle primitives from there has been deemed \
82        //   super inefficient. Note that this buffer would be shared with 'blit_bitmap()', and \
83        //   thus alpha-channel pixels would need to be blended accordingly.
84
85        Ok(())
86    }
87
88    fn draw_line<S: BackendStyle>(
89        &mut self,
90        from: BackendCoord,
91        to: BackendCoord,
92        style: &S,
93    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
94        // Acquire absolute position generator (in parent container)
95        if let Some(position) = position::PositionParent::from(&self.ui, self.parent) {
96            // Generate line style
97            let line_style = conrod::widget::primitive::line::Style::solid()
98                .color(color::Color::from(&style.color()).into())
99                .thickness(style.stroke_width() as ConrodScalar);
100
101            // Render line widget
102            conrod::widget::line::Line::abs_styled(
103                position.abs_point_conrod_scalar(&from),
104                position.abs_point_conrod_scalar(&to),
105                line_style,
106            )
107            .top_left_of(self.parent)
108            .set(self.graph.line.next(&mut self.ui), &mut self.ui);
109
110            Ok(())
111        } else {
112            Err(DrawingErrorKind::DrawingError(
113                ConrodBackendError::NoParentPosition,
114            ))
115        }
116    }
117
118    fn draw_rect<S: BackendStyle>(
119        &mut self,
120        upper_left: BackendCoord,
121        bottom_right: BackendCoord,
122        style: &S,
123        fill: bool,
124    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
125        // Generate rectangle style
126        let rectangle_style = if fill {
127            conrod::widget::primitive::shape::Style::fill_with(
128                color::Color::from(&style.color()).into(),
129            )
130        } else {
131            conrod::widget::primitive::shape::Style::outline_styled(
132                conrod::widget::primitive::line::Style::new()
133                    .color(color::Color::from(&style.color()).into())
134                    .thickness(style.stroke_width() as ConrodScalar),
135            )
136        };
137
138        // Render rectangle widget
139        conrod::widget::rectangle::Rectangle::styled(
140            [
141                (bottom_right.0 - upper_left.0) as ConrodScalar,
142                (bottom_right.1 - upper_left.1) as ConrodScalar,
143            ],
144            rectangle_style,
145        )
146        .top_left_with_margins_on(
147            self.parent,
148            upper_left.1 as ConrodScalar,
149            upper_left.0 as ConrodScalar,
150        )
151        .set(self.graph.rect.next(&mut self.ui), &mut self.ui);
152
153        Ok(())
154    }
155
156    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
157        &mut self,
158        path: I,
159        style: &S,
160    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
161        // Acquire absolute position generator (in parent container)
162        if let Some(position) = position::PositionParent::from(&self.ui, self.parent) {
163            // Generate line style
164            let line_style = conrod::widget::primitive::line::Style::solid()
165                .color(color::Color::from(&style.color()).into())
166                .thickness(style.stroke_width() as ConrodScalar);
167
168            // Render point path widget
169            conrod::widget::point_path::PointPath::abs_styled(
170                path.into_iter()
171                    .map(|point| position.abs_point_conrod_scalar(&point))
172                    .collect::<Vec<conrod::position::Point>>(),
173                line_style,
174            )
175            .top_left_of(self.parent)
176            .set(self.graph.path.next(&mut self.ui), &mut self.ui);
177
178            Ok(())
179        } else {
180            Err(DrawingErrorKind::DrawingError(
181                ConrodBackendError::NoParentPosition,
182            ))
183        }
184    }
185
186    fn draw_circle<S: BackendStyle>(
187        &mut self,
188        center: BackendCoord,
189        radius: u32,
190        style: &S,
191        fill: bool,
192    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
193        // Generate circle style
194        let circle_style = if fill {
195            conrod::widget::primitive::shape::Style::fill_with(
196                color::Color::from(&style.color()).into(),
197            )
198        } else {
199            conrod::widget::primitive::shape::Style::outline_styled(
200                conrod::widget::primitive::line::Style::new()
201                    .color(color::Color::from(&style.color()).into())
202                    .thickness(style.stroke_width() as ConrodScalar),
203            )
204        };
205
206        // Render circle widget
207        conrod::widget::circle::Circle::styled(radius as ConrodScalar, circle_style)
208            .top_left_with_margins_on(
209                self.parent,
210                (center.1 - radius as i32) as ConrodScalar,
211                (center.0 - radius as i32) as ConrodScalar,
212            )
213            .set(self.graph.circle.next(&mut self.ui), &mut self.ui);
214
215        Ok(())
216    }
217
218    fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
219        &mut self,
220        vert: I,
221        style: &S,
222    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
223        // Acquire absolute position generator (in parent container)
224        if let Some(position) = position::PositionParent::from(&self.ui, self.parent) {
225            // Paint a simplified path, where empty areas are removed and un-necessary points are \
226            //   cleared. This is required for triangulation to work properly, and it reduces \
227            //   the number of triangles on screen to a strict minimum.
228            let simplified_path: Vec<_> = path::PathSimplifier::from(
229                vert.into_iter()
230                    .map(|vertex| position.abs_point_path_simplifier(&vertex)),
231            )
232            .collect();
233
234            // Find closed shapes (eg. when the plot area goes from positive to negative, we need \
235            //   to split the path into two distinct paths, otherwise we will not be able to \
236            //   triangulate properly, and thus we will not be able to fill the shape)
237            if let Ok(mut shape_splitter) = shape::ShapeSplitter::try_from(&simplified_path) {
238                // Generate polygon style
239                let polygon_style = conrod::widget::primitive::shape::Style::fill_with(
240                    color::Color::from(&style.color()).into(),
241                );
242
243                // Triangulate the polygon points, giving back a list of triangles that can be \
244                //   filled into a contiguous area.
245                // Notice: this method takes into account concave shapes
246                for shape_points in shape_splitter.collect() {
247                    // Is that enough points to form at least a triangle?
248                    if shape_points.len() >= 3 {
249                        let triangles = triangulate::triangulate_points(shape_points.iter());
250
251                        for index in 0..triangles.size() {
252                            conrod::widget::polygon::Polygon::abs_styled(
253                                triangles.get_triangle(index).points.iter().copied(),
254                                polygon_style,
255                            )
256                            .top_left_of(self.parent)
257                            .set(self.graph.fill.next(&mut self.ui), &mut self.ui);
258                        }
259                    }
260                }
261            }
262
263            Ok(())
264        } else {
265            Err(DrawingErrorKind::DrawingError(
266                ConrodBackendError::NoParentPosition,
267            ))
268        }
269    }
270
271    fn draw_text<S: BackendTextStyle>(
272        &mut self,
273        text: &str,
274        style: &S,
275        pos: BackendCoord,
276    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
277        // Adapt font style from rasterizer style to Conrod
278        let (text_width_estimated, font_size_final) = convert::font_style(text, style.size());
279
280        // Generate text style
281        let mut text_style = conrod::widget::primitive::text::Style::default();
282
283        text_style.color = Some(color::Color::from(&style.color()).into());
284        text_style.font_id = Some(Some(self.font));
285        text_style.font_size = Some(font_size_final);
286
287        text_style.justify = Some(match style.anchor().h_pos {
288            text_anchor::HPos::Left => conrod::text::Justify::Left,
289            text_anchor::HPos::Right => conrod::text::Justify::Right,
290            text_anchor::HPos::Center => conrod::text::Justify::Center,
291        });
292
293        // Render text widget
294        conrod::widget::Text::new(text)
295            .with_style(text_style)
296            .top_left_with_margins_on(
297                self.parent,
298                pos.1 as ConrodScalar - (style.size() / 2.0 + 1.0),
299                pos.0 as ConrodScalar - text_width_estimated,
300            )
301            .set(self.graph.text.next(&mut self.ui), &mut self.ui);
302
303        Ok(())
304    }
305
306    fn estimate_text_size<S: BackendTextStyle>(
307        &self,
308        text: &str,
309        style: &S,
310    ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
311        let (text_width_estimated, text_height_estimated) = convert::font_style(text, style.size());
312
313        // Return as (size_on_x, size_on_y)
314        Ok((text_width_estimated as u32, text_height_estimated))
315    }
316
317    fn blit_bitmap(
318        &mut self,
319        _pos: BackendCoord,
320        (_iw, _ih): (u32, u32),
321        _src: &[u8],
322    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
323        // Not supported yet (rendering ignored)
324        // Notice: doing this efficiently would require building an internal buffer on 'self', and \
325        //   rendering it as a Conrod image widget when the final call to 'present()' is done. \
326        //   Note that this buffer would be shared with 'draw_pixel()', and thus alpha-channel \
327        //   pixels would need to be blended accordingly.
328
329        Ok(())
330    }
331}