plotters_unsable/drawing/
area.rs

1/// The abstraction of a drawing area
2use super::backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
3use crate::coord::{CoordTranslate, MeshLine, Ranged, RangedCoord, Shift};
4use crate::element::{Drawable, PointCollection};
5use crate::style::{Color, TextStyle};
6
7use std::borrow::Borrow;
8use std::cell::RefCell;
9use std::error::Error;
10use std::iter::{once, repeat};
11use std::ops::Range;
12use std::rc::Rc;
13
14/// The representation of the rectangle in backend canvas
15#[derive(Clone, Debug)]
16struct Rect {
17    x0: i32,
18    y0: i32,
19    x1: i32,
20    y1: i32,
21}
22
23impl Rect {
24    /// Split the rectangle into a few smaller rectnagles
25    fn split<'a, BPI: IntoIterator<Item = &'a i32> + 'a>(
26        &'a self,
27        break_points: BPI,
28        vertical: bool,
29    ) -> impl Iterator<Item = Rect> + 'a {
30        let (mut x0, mut y0) = (self.x0, self.y0);
31        let (full_x, full_y) = (self.x1, self.y1);
32        break_points
33            .into_iter()
34            .chain(once(if vertical { &self.y1 } else { &self.x1 }))
35            .map(move |&p| {
36                let x1 = if vertical { full_x } else { p };
37                let y1 = if vertical { p } else { full_y };
38                let ret = Rect { x0, y0, x1, y1 };
39
40                if vertical {
41                    y0 = y1
42                } else {
43                    x0 = x1;
44                }
45
46                ret
47            })
48    }
49
50    /// Evently split the regtangle to a row * col mesh
51    fn split_evenly<'a>(&'a self, (row, col): (usize, usize)) -> impl Iterator<Item = Rect> + 'a {
52        fn compute_evenly_split(from: i32, to: i32, n: usize, idx: usize) -> i32 {
53            let size = (to - from) as usize;
54            from + idx as i32 * (size / n) as i32 + if size % n < idx { 1 } else { 0 }
55        }
56        (0..row)
57            .map(move |x| repeat(x).zip(0..col))
58            .flatten()
59            .map(move |(ri, ci)| Self {
60                y0: compute_evenly_split(self.y0, self.y1, row, ri),
61                y1: compute_evenly_split(self.y0, self.y1, row, ri + 1),
62                x0: compute_evenly_split(self.x0, self.x1, col, ci),
63                x1: compute_evenly_split(self.x0, self.x1, col, ci + 1),
64            })
65    }
66
67    /// Make the coordinate in the range of the rectangle
68    fn truncate(&self, p: (i32, i32)) -> (i32, i32) {
69        (p.0.min(self.x1).max(self.x0), p.1.min(self.y1).max(self.y0))
70    }
71}
72
73/// The abstraction of a region
74pub struct DrawingArea<DB: DrawingBackend, CT: CoordTranslate> {
75    backend: Rc<RefCell<DB>>,
76    rect: Rect,
77    coord: CT,
78}
79
80impl<DB: DrawingBackend, CT: CoordTranslate + Clone> Clone for DrawingArea<DB, CT> {
81    fn clone(&self) -> Self {
82        Self {
83            backend: self.copy_backend_ref(),
84            rect: self.rect.clone(),
85            coord: self.coord.clone(),
86        }
87    }
88}
89
90/// The error description of any drawing area API
91#[derive(Debug)]
92pub enum DrawingAreaErrorKind<E: Error> {
93    /// The error is due to drawing backend failure
94    BackendError(DrawingErrorKind<E>),
95    /// We are not able to get the mutable reference of the backend,
96    /// which indicates the drawing backend is current used by other
97    /// drawing operation
98    SharingError,
99    /// The error caused by invalid layout
100    LayoutError,
101}
102
103impl<E: Error> std::fmt::Display for DrawingAreaErrorKind<E> {
104    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
105        match self {
106            DrawingAreaErrorKind::BackendError(e) => write!(fmt, "backend error: {}", e),
107            DrawingAreaErrorKind::SharingError => {
108                write!(fmt, "Mulitple backend operation in progress")
109            }
110            DrawingAreaErrorKind::LayoutError => write!(fmt, "Bad layout"),
111        }
112    }
113}
114
115impl<E: Error> Error for DrawingAreaErrorKind<E> {}
116
117#[allow(type_alias_bounds)]
118type DrawingAreaError<T: DrawingBackend> = DrawingAreaErrorKind<T::ErrorType>;
119
120impl<DB: DrawingBackend> From<DB> for DrawingArea<DB, Shift> {
121    fn from(backend: DB) -> Self {
122        let (x1, y1) = backend.get_size();
123        Self {
124            rect: Rect {
125                x0: 0,
126                y0: 0,
127                x1: x1 as i32,
128                y1: y1 as i32,
129            },
130            backend: Rc::new(RefCell::new(backend)),
131            coord: Shift((0, 0)),
132        }
133    }
134}
135
136/// A type which can be converted into a root drawing area
137pub trait IntoDrawingArea: DrawingBackend + Sized {
138    /// Convert the type into a root drawing area
139    fn into_drawing_area(self) -> DrawingArea<Self, Shift>;
140}
141
142impl<T: DrawingBackend> IntoDrawingArea for T {
143    fn into_drawing_area(self) -> DrawingArea<T, Shift> {
144        self.into()
145    }
146}
147
148impl<DB: DrawingBackend, X: Ranged, Y: Ranged> DrawingArea<DB, RangedCoord<X, Y>> {
149    /// Draw the mesh on a area
150    pub fn draw_mesh<DrawFunc>(
151        &self,
152        mut draw_func: DrawFunc,
153        y_count_max: usize,
154        x_count_max: usize,
155    ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
156    where
157        DrawFunc: FnMut(&mut DB, MeshLine<X, Y>) -> Result<(), DrawingErrorKind<DB::ErrorType>>,
158    {
159        self.backend_ops(move |b| {
160            self.coord
161                .draw_mesh(y_count_max, x_count_max, |line| draw_func(b, line))
162        })
163    }
164
165    /// Get the range of X of the guest coordinate for current drawing area
166    pub fn get_x_range(&self) -> Range<X::ValueType> {
167        self.coord.get_x_range()
168    }
169
170    /// Get the range of Y of the guest coordinate for current drawing area
171    pub fn get_y_range(&self) -> Range<Y::ValueType> {
172        self.coord.get_y_range()
173    }
174}
175
176impl<DB: DrawingBackend, CT: CoordTranslate> DrawingArea<DB, CT> {
177    /// Get the left upper conner of this area in the drawing backend
178    pub fn get_base_pixel(&self) -> BackendCoord {
179        (self.rect.x0, self.rect.y0)
180    }
181
182    /// Strip the applied coordinate specification and returns a shift-based drawing area
183    pub fn strip_coord_spec(&self) -> DrawingArea<DB, Shift> {
184        DrawingArea {
185            rect: self.rect.clone(),
186            backend: self.copy_backend_ref(),
187            coord: Shift((self.rect.x0, self.rect.y0)),
188        }
189    }
190
191    /// Get the area dimension in pixel
192    pub fn dim_in_pixel(&self) -> (u32, u32) {
193        (
194            (self.rect.x1 - self.rect.x0) as u32,
195            (self.rect.y1 - self.rect.y0) as u32,
196        )
197    }
198
199    /// Get the pixel range of this area
200    pub fn get_pixel_range(&self) -> (Range<i32>, Range<i32>) {
201        (self.rect.x0..self.rect.x1, self.rect.y0..self.rect.y1)
202    }
203
204    /// Copy the drawing contenxt
205    fn copy_backend_ref(&self) -> Rc<RefCell<DB>> {
206        self.backend.clone()
207    }
208
209    /// Perform operation on the drawing backend
210    fn backend_ops<R, O: FnOnce(&mut DB) -> Result<R, DrawingErrorKind<DB::ErrorType>>>(
211        &self,
212        ops: O,
213    ) -> Result<R, DrawingAreaError<DB>> {
214        if let Ok(mut db) = self.backend.try_borrow_mut() {
215            db.ensure_prepared()
216                .map_err(DrawingAreaErrorKind::BackendError)?;
217            ops(&mut db).map_err(DrawingAreaErrorKind::BackendError)
218        } else {
219            Err(DrawingAreaErrorKind::SharingError)
220        }
221    }
222
223    /// Fill the entire drawing area with a color
224    pub fn fill<ColorType: Color>(&self, color: &ColorType) -> Result<(), DrawingAreaError<DB>> {
225        self.backend_ops(|backend| {
226            backend.draw_rect(
227                (self.rect.x0, self.rect.y0),
228                (self.rect.x1, self.rect.y1),
229                color,
230                true,
231            )
232        })
233    }
234
235    /// Draw a single pixel
236    pub fn draw_pixel<ColorType: Color>(
237        &self,
238        pos: CT::From,
239        color: &ColorType,
240    ) -> Result<(), DrawingAreaError<DB>> {
241        let pos = self.coord.translate(&pos);
242        self.backend_ops(|b| b.draw_pixel(pos, color))
243    }
244
245    /// Present all the pending changes to the backend
246    pub fn present(&self) -> Result<(), DrawingAreaError<DB>> {
247        self.backend_ops(|b| b.present())
248    }
249
250    /// Draw an high-level element
251    pub fn draw<'a, E>(&self, element: &'a E) -> Result<(), DrawingAreaError<DB>>
252    where
253        &'a E: PointCollection<'a, CT::From>,
254        E: Drawable<DB>,
255    {
256        let backend_coords = element.point_iter().into_iter().map(|p| {
257            let b = p.borrow();
258            self.rect.truncate(self.coord.translate(b))
259        });
260        self.backend_ops(move |b| element.draw(backend_coords, b))
261    }
262
263    /// Map coordinate to the backend coordinate
264    pub fn map_coordinate(&self, coord: &CT::From) -> BackendCoord {
265        self.coord.translate(coord)
266    }
267}
268
269impl<DB: DrawingBackend> DrawingArea<DB, Shift> {
270    /// Shrink the region, note all the locaitions are in guest coordinate
271    pub fn shrink(
272        mut self,
273        left_upper: (u32, u32),
274        dimension: (u32, u32),
275    ) -> DrawingArea<DB, Shift> {
276        self.rect.x0 = self.rect.x1.min(self.rect.x0 + left_upper.0 as i32);
277        self.rect.y0 = self.rect.y1.min(self.rect.y0 + left_upper.1 as i32);
278
279        self.rect.x1 = self.rect.x0.max(self.rect.x0 + dimension.0 as i32);
280        self.rect.y1 = self.rect.y0.max(self.rect.y0 + dimension.1 as i32);
281
282        self.coord = Shift((self.rect.x0, self.rect.y0));
283
284        self
285    }
286
287    /// Apply a new coord transformation object and returns a new drawing area
288    pub fn apply_coord_spec<CT: CoordTranslate>(&self, coord_spec: CT) -> DrawingArea<DB, CT> {
289        DrawingArea {
290            rect: self.rect.clone(),
291            backend: self.copy_backend_ref(),
292            coord: coord_spec,
293        }
294    }
295
296    /// Create a margin for the given drawing area and returns the new drawing area
297    pub fn margin(&self, top: i32, bottom: i32, left: i32, right: i32) -> DrawingArea<DB, Shift> {
298        DrawingArea {
299            rect: Rect {
300                x0: self.rect.x0 + left,
301                y0: self.rect.y0 + top,
302                x1: self.rect.x1 - right,
303                y1: self.rect.y1 - bottom,
304            },
305            backend: self.copy_backend_ref(),
306            coord: Shift((self.rect.x0 + left, self.rect.y0 + top)),
307        }
308    }
309
310    /// Split the drawing area vertically
311    pub fn split_vertically(&self, y: i32) -> (Self, Self) {
312        let split_point = [y + self.rect.y0];
313        let mut ret = self.rect.split(split_point.iter(), true).map(|rect| Self {
314            rect: rect.clone(),
315            backend: self.copy_backend_ref(),
316            coord: Shift((rect.x0, rect.y0)),
317        });
318
319        (ret.next().unwrap(), ret.next().unwrap())
320    }
321
322    /// Split the drawing area horizentally
323    pub fn split_horizentally(&self, x: i32) -> (Self, Self) {
324        let split_point = [x + self.rect.x0];
325        let mut ret = self.rect.split(split_point.iter(), false).map(|rect| Self {
326            rect: rect.clone(),
327            backend: self.copy_backend_ref(),
328            coord: Shift((rect.x0, rect.y0)),
329        });
330
331        (ret.next().unwrap(), ret.next().unwrap())
332    }
333
334    /// Split the drawing area evenly
335    pub fn split_evenly(&self, (row, col): (usize, usize)) -> Vec<Self> {
336        self.rect
337            .split_evenly((row, col))
338            .map(|rect| Self {
339                rect: rect.clone(),
340                backend: self.copy_backend_ref(),
341                coord: Shift((rect.x0, rect.y0)),
342            })
343            .collect()
344    }
345
346    /// Draw a title of the drawing area and return the remaining drawing area
347    pub fn titled<'a, S: Into<TextStyle<'a>>>(
348        &self,
349        text: &str,
350        style: S,
351    ) -> Result<Self, DrawingAreaError<DB>> {
352        let style = style.into();
353
354        let (text_w, text_h) = match style.font.box_size(text) {
355            Ok(what) => what,
356            Err(what) => {
357                return Err(DrawingAreaErrorKind::BackendError(
358                    DrawingErrorKind::FontError(what),
359                ));
360            }
361        };
362        let padding = if self.rect.x1 - self.rect.x0 > text_w as i32 {
363            (self.rect.x1 - self.rect.x0 - text_w as i32) / 2
364        } else {
365            0
366        };
367
368        self.backend_ops(|b| {
369            b.draw_text(
370                text,
371                style.font,
372                (self.rect.x0 + padding, self.rect.y0 + 5),
373                &Box::new(style.color),
374            )
375        })?;
376
377        Ok(Self {
378            rect: Rect {
379                x0: self.rect.x0,
380                y0: self.rect.y0 + 10 + text_h as i32,
381                x1: self.rect.x1,
382                y1: self.rect.y1,
383            },
384            backend: self.copy_backend_ref(),
385            coord: Shift((self.rect.x0, self.rect.y0 + 10 + text_h as i32)),
386        })
387    }
388
389    /// Draw text on the drawing area
390    pub fn draw_text(
391        &self,
392        text: &str,
393        style: &TextStyle,
394        pos: BackendCoord,
395    ) -> Result<(), DrawingAreaError<DB>> {
396        self.backend_ops(|b| {
397            b.draw_text(
398                text,
399                style.font,
400                (pos.0 + self.rect.x0, pos.1 + self.rect.y0),
401                &Box::new(style.color),
402            )
403        })
404    }
405}
406
407impl<DB: DrawingBackend, CT: CoordTranslate> DrawingArea<DB, CT> {
408    pub fn into_coord_spec(self) -> CT {
409        self.coord
410    }
411}