conrod_core/widget/primitive/shape/
polygon.rs

1//! A simple, non-interactive **Polygon** widget for drawing arbitrary convex shapes.
2
3use super::Style;
4use graph;
5use utils::{bounding_box_for_points, vec2_add, vec2_sub};
6use widget;
7use widget::triangles::Triangle;
8use {Color, Colorable, Point, Positionable, Sizeable, Theme, Widget};
9
10/// A basic, non-interactive, arbitrary **Polygon** widget.
11///
12/// The **Polygon** is described by specifying its corners in order.
13///
14/// **Polygon** will automatically close all shapes, so the given list of points does not need to
15/// start and end with the same position.
16#[derive(Copy, Clone, Debug, WidgetCommon_)]
17pub struct Polygon<I> {
18    /// Data necessary and common for all widget builder types.
19    #[conrod(common_builder)]
20    pub common: widget::CommonBuilder,
21    /// The points describing the corners of the **Polygon**.
22    pub points: I,
23    /// Unique styling for the **Polygon**.
24    pub style: Style,
25    /// Whether or not the points should be automatically centred to the widget position.
26    pub maybe_shift_to_centre_from: Option<Point>,
27}
28
29/// Unique state for the **Polygon**.
30#[derive(Clone, Debug, PartialEq)]
31pub struct State {
32    /// Whether the rectangle is drawn as an outline or a filled color.
33    kind: Kind,
34    /// An owned version of the points yielded by the **Polygon**'s `points` iterator.
35    pub points: Vec<Point>,
36}
37
38/// Whether the rectangle is drawn as an outline or a filled color.
39#[derive(Copy, Clone, Debug, PartialEq)]
40pub enum Kind {
41    /// Only the outline of the rectangle is drawn.
42    Outline,
43    /// The rectangle area is filled with some color.
44    Fill,
45}
46
47/// An iterator that triangulates a polygon represented by a sequence of points describing its
48/// edges.
49#[derive(Clone)]
50pub struct Triangles<I> {
51    first: Point,
52    prev: Point,
53    points: I,
54}
55
56impl<I> Polygon<I> {
57    /// Build a polygon with the given points and style.
58    pub fn styled(points: I, style: Style) -> Self {
59        Polygon {
60            points: points,
61            common: widget::CommonBuilder::default(),
62            style: style,
63            maybe_shift_to_centre_from: None,
64        }
65    }
66
67    /// Build a **Polygon** with the default **Fill** style.
68    pub fn fill(points: I) -> Self {
69        Polygon::styled(points, Style::fill())
70    }
71
72    /// Build a **Polygon** **Fill**ed with the given **Color**.
73    pub fn fill_with(points: I, color: Color) -> Self {
74        Polygon::styled(points, Style::fill_with(color))
75    }
76
77    /// Build a **Polygon** with the default **Outline** style.
78    pub fn outline(points: I) -> Self {
79        Polygon::styled(points, Style::outline())
80    }
81
82    /// Build a **Polygon** **Outline**ed with the given line style.
83    pub fn outline_styled(points: I, style: widget::line::Style) -> Self {
84        Polygon::styled(points, Style::outline_styled(style))
85    }
86
87    /// Build a new filled **Polygon** whose bounding box is fit to the absolute co-ordinates of
88    /// the points.
89    ///
90    /// This requires that the `points` iterator is `Clone` so that we may iterate through and
91    /// determine the bounding box of the `points`.
92    ///
93    /// If you would rather centre the points to the middle of the bounding box, use
94    /// the [**Polygon::centred**](./struct.Polygon#method.centred) methods instead.
95    pub fn abs_styled(points: I, style: Style) -> Self
96    where
97        I: IntoIterator<Item = Point> + Clone,
98    {
99        let points_clone = points.clone().into_iter();
100        let (xy, dim) = bounding_box_for_points(points_clone).xy_dim();
101        Polygon::styled(points, style).wh(dim).xy(xy)
102    }
103
104    /// The same as [**Polygon::abs_styled**](./struct.Polygon#method.abs_styled) but builds the
105    /// **Polygon** with the default **Fill** style.
106    pub fn abs_fill(points: I) -> Self
107    where
108        I: IntoIterator<Item = Point> + Clone,
109    {
110        Polygon::abs_styled(points, Style::fill())
111    }
112
113    /// The same as [**Polygon::abs_styled**](./struct.Polygon#method.abs_styled) but builds the
114    /// **Polygon** **Fill**ed with the given **Color**.
115    pub fn abs_fill_with(points: I, color: Color) -> Self
116    where
117        I: IntoIterator<Item = Point> + Clone,
118    {
119        Polygon::abs_styled(points, Style::fill_with(color))
120    }
121
122    /// The same as [**Polygon::abs_styled**](./struct.Polygon#method.abs_styled) but builds the
123    /// **Polygon** with the default **Outline** style.
124    pub fn abs_outline(points: I) -> Self
125    where
126        I: IntoIterator<Item = Point> + Clone,
127    {
128        Polygon::abs_styled(points, Style::outline())
129    }
130
131    /// The same as [**Polygon::abs_styled**](./struct.Polygon#method.abs_styled) but builds the
132    /// **Polygon** with the given **Outline** styling.
133    pub fn abs_outline_styled(points: I, style: widget::line::Style) -> Self
134    where
135        I: IntoIterator<Item = Point> + Clone,
136    {
137        Polygon::abs_styled(points, Style::outline_styled(style))
138    }
139
140    /// Build a new **Polygon** and shift the location of the points so that the centre of their
141    /// bounding rectangle lies at the position determined for the **Polygon** widget.
142    ///
143    /// This is useful if your points simply describe a shape and you want to position them using
144    /// conrod's auto-layout and/or **Positionable** methods.
145    ///
146    /// If you would rather centre the bounding box to the points, use the
147    /// [**Polygon::abs**](./struct.Polygon#method.abs) constructor method instead.
148    pub fn centred_styled(points: I, style: Style) -> Self
149    where
150        I: IntoIterator<Item = Point> + Clone,
151    {
152        let points_clone = points.clone().into_iter();
153        let (xy, dim) = bounding_box_for_points(points_clone).xy_dim();
154        let mut polygon = Polygon::styled(points, style).wh(dim);
155        polygon.maybe_shift_to_centre_from = Some(xy);
156        polygon
157    }
158
159    /// The same as [**Polygon::centred_styled**](./struct.Polygon#method.centred_styled) but
160    /// constructs the **Polygon** with the default **Fill** style.
161    pub fn centred_fill(points: I) -> Self
162    where
163        I: IntoIterator<Item = Point> + Clone,
164    {
165        Polygon::centred_styled(points, Style::fill())
166    }
167
168    /// The same as [**Polygon::centred_styled**](./struct.Polygon#method.centred_styled) but
169    /// constructs the **Polygon** **Fill**ed with the given color.
170    pub fn centred_fill_with(points: I, color: Color) -> Self
171    where
172        I: IntoIterator<Item = Point> + Clone,
173    {
174        Polygon::centred_styled(points, Style::fill_with(color))
175    }
176
177    /// The same as [**Polygon::centred_styled**](./struct.Polygon#method.centred_styled) but
178    /// constructs the **Polygon** with the default **Outline** style.
179    pub fn centred_outline(points: I) -> Self
180    where
181        I: IntoIterator<Item = Point> + Clone,
182    {
183        Polygon::centred_styled(points, Style::outline())
184    }
185
186    /// The same as [**Polygon::centred_styled**](./struct.Polygon#method.centred_styled) but
187    /// constructs the **Polygon** **Outline**d with the given styling.
188    pub fn centred_outline_styled(points: I, style: widget::line::Style) -> Self
189    where
190        I: IntoIterator<Item = Point> + Clone,
191    {
192        Polygon::centred_styled(points, Style::outline_styled(style))
193    }
194}
195
196impl<I> Widget for Polygon<I>
197where
198    I: IntoIterator<Item = Point>,
199{
200    type State = State;
201    type Style = Style;
202    type Event = ();
203
204    fn init_state(&self, _: widget::id::Generator) -> Self::State {
205        State {
206            kind: Kind::Fill,
207            points: Vec::new(),
208        }
209    }
210
211    fn style(&self) -> Self::Style {
212        self.style.clone()
213    }
214
215    fn is_over(&self) -> widget::IsOverFn {
216        is_over_widget
217    }
218
219    /// Update the state of the Polygon.
220    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
221        use utils::{iter_diff, IterDiff};
222        let widget::UpdateArgs {
223            rect, state, style, ..
224        } = args;
225        let Polygon {
226            points,
227            maybe_shift_to_centre_from,
228            ..
229        } = self;
230
231        // A function that compares the given points iterator to the points currently owned by
232        // `State` and updates only if necessary.
233        fn update_points<I>(state: &mut widget::State<State>, points: I)
234        where
235            I: IntoIterator<Item = Point>,
236        {
237            match iter_diff(&state.points, points) {
238                Some(IterDiff::FirstMismatch(i, mismatch)) => state.update(|state| {
239                    state.points.truncate(i);
240                    state.points.extend(mismatch);
241                }),
242                Some(IterDiff::Longer(remaining)) => {
243                    state.update(|state| state.points.extend(remaining))
244                }
245                Some(IterDiff::Shorter(total)) => {
246                    state.update(|state| state.points.truncate(total))
247                }
248                None => (),
249            }
250        }
251
252        // Check whether or not we need to centre the points.
253        match maybe_shift_to_centre_from {
254            Some(original) => {
255                let xy = rect.xy();
256                let difference = vec2_sub(xy, original);
257                update_points(
258                    state,
259                    points.into_iter().map(|point| vec2_add(point, difference)),
260                )
261            }
262            None => update_points(state, points),
263        }
264
265        let kind = match *style {
266            Style::Fill(_) => Kind::Fill,
267            Style::Outline(_) => Kind::Outline,
268        };
269
270        if state.kind != kind {
271            state.update(|state| state.kind = kind);
272        }
273    }
274}
275
276impl<I> Colorable for Polygon<I> {
277    fn color(mut self, color: Color) -> Self {
278        self.style.set_color(color);
279        self
280    }
281}
282
283/// Triangulate the polygon given as a list of `Point`s describing its sides.
284///
285/// Returns `None` if the given iterator yields less than two points.
286pub fn triangles<I>(points: I) -> Option<Triangles<I::IntoIter>>
287where
288    I: IntoIterator<Item = Point>,
289{
290    let mut points = points.into_iter();
291    let first = match points.next() {
292        Some(p) => p,
293        None => return None,
294    };
295    let prev = match points.next() {
296        Some(p) => p,
297        None => return None,
298    };
299    Some(Triangles {
300        first: first,
301        prev: prev,
302        points: points,
303    })
304}
305
306impl<I> Iterator for Triangles<I>
307where
308    I: Iterator<Item = Point>,
309{
310    type Item = Triangle<Point>;
311    fn next(&mut self) -> Option<Self::Item> {
312        self.points.next().map(|point| {
313            let t = Triangle([self.first, self.prev, point]);
314            self.prev = point;
315            t
316        })
317    }
318}
319
320/// Returns `true` if the given `Point` is over the polygon described by the given series of
321/// points.
322pub fn is_over<I>(points: I, point: Point) -> bool
323where
324    I: IntoIterator<Item = Point>,
325{
326    triangles(points)
327        .map(|ts| widget::triangles::is_over(ts, point))
328        .unwrap_or(false)
329}
330
331/// The function to use for picking whether a given point is over the polygon.
332pub fn is_over_widget(widget: &graph::Container, point: Point, _: &Theme) -> widget::IsOver {
333    widget
334        .state_and_style::<State, Style>()
335        .map(|widget| is_over(widget.state.points.iter().cloned(), point))
336        .unwrap_or_else(|| widget.rect.is_over(point))
337        .into()
338}