graphics/
rectangle.rs

1//! Draw rectangle
2//!
3//! This module contains the definintion of a rectangle with possibly rounded
4//! corners. It contains the code to draw the rectangle and defines properties
5//! like color and shape. The rectangle dimensions and location are specified by
6//! `types::Rectangle`.
7//!
8//! To draw a square with side 10 and top left corner in (0, 0), do the
9//! following:
10//! ```ignore
11//! let rectangle = Rectangle::new(color::BLACK);
12//! let dims = square(0.0, 0.0, 10.0);
13//! rectangle.draw(dims, &draw_state::Default::default(), transform, g);
14//! ```
15
16pub use crate::math::margin_rectangle as margin;
17use crate::{
18    math::{Matrix2d, Scalar},
19    triangulation, types,
20    types::{Color, Radius, Resolution},
21    DrawState, Graphics,
22};
23
24/// Create `types::Rectangle` by the two opposite corners.
25///
26/// The corners are in (x0, y0) and (x1, y1).
27pub fn rectangle_by_corners(x0: Scalar, y0: Scalar, x1: Scalar, y1: Scalar) -> types::Rectangle {
28    let (xmin, w) = if x0 <= x1 {
29        (x0, x1 - x0)
30    } else {
31        (x1, x0 - x1)
32    };
33
34    let (ymin, h) = if y0 <= y1 {
35        (y0, y1 - y0)
36    } else {
37        (y1, y0 - y1)
38    };
39
40    [xmin, ymin, w, h]
41}
42
43/// Create a centered rectangle.
44///
45/// `rect` is interpreted as `[x, y, half_width, half_height]`
46///
47/// Note that passing an existing `types::Rectangle` in here will not produce a rectangle of the
48/// same
49/// dimensions
50///
51/// # Example
52/// ```
53/// use graphics::rectangle::centered;
54///
55/// // We create a rectangle centered on [0.0, 0.0] with width 2.0 and height 6.0
56/// let centered_rect = centered([0.0, 0.0, 1.0, 3.0]);
57/// assert_eq!(centered_rect, [-1.0, -3.0, 2.0, 6.0]);
58/// ```
59pub fn centered(rect: types::Rectangle) -> types::Rectangle {
60    [
61        rect[0] - rect[2],
62        rect[1] - rect[3],
63        2.0 * rect[2],
64        2.0 * rect[3],
65    ]
66}
67
68/// Create `types::Rectangle` for a square with a center in (`x`, `y`) and side
69/// `2 * radius`.
70pub fn centered_square(x: Scalar, y: Scalar, radius: Scalar) -> types::Rectangle {
71    [x - radius, y - radius, 2.0 * radius, 2.0 * radius]
72}
73
74/// Create `types::Rectangle` for a square with a top-left corner in (`x`, `y`)
75/// and side `size`.
76pub fn square(x: Scalar, y: Scalar, size: Scalar) -> types::Rectangle {
77    [x, y, size, size]
78}
79
80/// The shape of the rectangle corners
81#[derive(Copy, Clone)]
82pub enum Shape {
83    /// Square corners
84    Square,
85    /// Round corners, with resolution per corner.
86    Round(Radius, Resolution),
87    /// Bevel corners
88    Bevel(Radius),
89}
90
91/// The border of the rectangle
92#[derive(Copy, Clone)]
93pub struct Border {
94    /// The color of the border
95    pub color: Color,
96    /// The radius of the border. The half-width of the line by which border is
97    /// drawn.
98    pub radius: Radius,
99}
100
101/// A filled rectangle
102#[derive(Copy, Clone)]
103pub struct Rectangle {
104    /// The rectangle color
105    pub color: Color,
106    /// The roundness of the rectangle
107    pub shape: Shape,
108    /// The border
109    pub border: Option<Border>,
110}
111
112impl Rectangle {
113    /// Creates a new rectangle.
114    pub fn new(color: Color) -> Rectangle {
115        Rectangle {
116            color,
117            shape: Shape::Square,
118            border: None,
119        }
120    }
121
122    /// Creates a new rectangle with rounded corners.
123    pub fn new_round(color: Color, round_radius: Radius) -> Rectangle {
124        Rectangle {
125            color,
126            shape: Shape::Round(round_radius, 32),
127            border: None,
128        }
129    }
130
131    /// Creates a new rectangle border.
132    pub fn new_border(color: Color, radius: Radius) -> Rectangle {
133        Rectangle {
134            color: [0.0; 4],
135            shape: Shape::Square,
136            border: Some(Border { color, radius }),
137        }
138    }
139
140    /// Creates a new rectangle border with rounded corners.
141    pub fn new_round_border(
142        color: Color,
143        round_radius: Radius,
144        border_radius: Radius,
145    ) -> Rectangle {
146        Rectangle {
147            color: [0.0; 4],
148            shape: Shape::Round(round_radius, 32),
149            border: Some(Border {
150                color,
151                radius: border_radius,
152            }),
153        }
154    }
155
156    /// Sets color.
157    pub fn color(mut self, value: Color) -> Self {
158        self.color = value;
159        self
160    }
161
162    /// Sets shape of the corners.
163    pub fn shape(mut self, value: Shape) -> Self {
164        self.shape = value;
165        self
166    }
167
168    /// Sets border properties.
169    pub fn border(mut self, value: Border) -> Self {
170        self.border = Some(value);
171        self
172    }
173
174    /// Sets optional border.
175    pub fn maybe_border(mut self, value: Option<Border>) -> Self {
176        self.border = value;
177        self
178    }
179
180    /// Draws the rectangle by corners using the default method.
181    #[inline(always)]
182    pub fn draw_from_to<P: Into<types::Vec2d>, G>(
183        &self,
184        from: P,
185        to: P,
186        draw_state: &DrawState,
187        transform: Matrix2d,
188        g: &mut G,
189    ) where
190        G: Graphics,
191    {
192        let from = from.into();
193        let to = to.into();
194        g.rectangle(
195            self,
196            rectangle_by_corners(from[0], from[1], to[0], to[1]),
197            draw_state,
198            transform,
199        );
200    }
201
202    /// Draws the rectangle using the default method.
203    ///
204    /// `rectangle` defines the rectangle's location and dimensions,
205    /// `draw_state` draw state, `draw_state::Default::default()` can be used
206    /// as a default, `transform` is the transformation matrix, `g` is the
207    /// `Graphics` implementation, that is used to actually draw the rectangle.s
208    #[inline(always)]
209    pub fn draw<R: Into<types::Rectangle>, G>(
210        &self,
211        rectangle: R,
212        draw_state: &DrawState,
213        transform: Matrix2d,
214        g: &mut G,
215    ) where
216        G: Graphics,
217    {
218        g.rectangle(self, rectangle, draw_state, transform);
219    }
220
221    /// Draws the rectangle using triangulation.
222    ///
223    /// This is the default implementation of draw() that will be used if `G`
224    /// does not redefine `Graphics::rectangle()`.
225    pub fn draw_tri<R: Into<types::Rectangle>, G>(
226        &self,
227        rectangle: R,
228        draw_state: &DrawState,
229        transform: Matrix2d,
230        g: &mut G,
231    ) where
232        G: Graphics,
233    {
234        let rectangle = rectangle.into();
235        if self.color[3] != 0.0 {
236            match self.shape {
237                Shape::Square => {
238                    g.tri_list(draw_state, &self.color, |f| {
239                        f(&triangulation::rect_tri_list_xy(transform, rectangle))
240                    });
241                }
242                Shape::Round(round_radius, resolution) => {
243                    g.tri_list(draw_state, &self.color, |f| {
244                        triangulation::with_round_rectangle_tri_list(
245                            resolution,
246                            transform,
247                            rectangle,
248                            round_radius,
249                            |vertices| f(vertices),
250                        )
251                    });
252                }
253                Shape::Bevel(bevel_radius) => {
254                    g.tri_list(draw_state, &self.color, |f| {
255                        triangulation::with_round_rectangle_tri_list(
256                            2,
257                            transform,
258                            rectangle,
259                            bevel_radius,
260                            |vertices| f(vertices),
261                        )
262                    });
263                }
264            }
265        }
266
267        if let Some(Border {
268            color,
269            radius: border_radius,
270        }) = self.border
271        {
272            if color[3] == 0.0 {
273                return;
274            }
275            match self.shape {
276                Shape::Square => {
277                    g.tri_list(draw_state, &color, |f| {
278                        f(&triangulation::rect_border_tri_list_xy(
279                            transform,
280                            rectangle,
281                            border_radius,
282                        ))
283                    });
284                }
285                Shape::Round(round_radius, resolution) => {
286                    g.tri_list(draw_state, &color, |f| {
287                        triangulation::with_round_rectangle_border_tri_list(
288                            resolution,
289                            transform,
290                            rectangle,
291                            round_radius,
292                            border_radius,
293                            |vertices| f(vertices),
294                        )
295                    });
296                }
297                Shape::Bevel(bevel_radius) => {
298                    g.tri_list(draw_state, &color, |f| {
299                        triangulation::with_round_rectangle_border_tri_list(
300                            2,
301                            transform,
302                            rectangle,
303                            bevel_radius,
304                            border_radius,
305                            |vertices| f(vertices),
306                        )
307                    });
308                }
309            }
310        }
311    }
312}
313
314#[cfg(test)]
315mod test {
316    use super::*;
317
318    #[test]
319    fn test_rectangle() {
320        let _rectangle = Rectangle::new([1.0; 4])
321            .color([0.0; 4])
322            .shape(Shape::Round(10.0, 32))
323            .border(Border {
324                color: [0.0; 4],
325                radius: 4.0,
326            });
327    }
328
329    #[test]
330    fn test_rectangle_by_corners() {
331        assert_eq!(
332            rectangle_by_corners(1.0, -1.0, 2.0, 3.0),
333            [1.0, -1.0, 1.0, 4.0]
334        );
335        assert_eq!(
336            rectangle_by_corners(2.0, 3.0, 1.0, -1.0),
337            [1.0, -1.0, 1.0, 4.0]
338        );
339    }
340}