conrod_core/widget/
grid.rs

1//! A widget for displaying a grid of lines across two axes.
2
3use utils::map_range;
4use widget::{self, CommonBuilder, UpdateArgs};
5use {Color, Colorable, Point, Scalar, Widget};
6
7/// A widget for displaying a grid of lines across two axes.
8#[derive(Copy, Clone, Debug, WidgetCommon_)]
9pub struct Grid<X, Y, I> {
10    /// Builder parameters that are common to all `Widget`s.
11    #[conrod(common_builder)]
12    pub common: CommonBuilder,
13    /// Unique styling parameters for the `Grid` widget.
14    pub style: Style,
15    /// The minimum visible bound along the *x* axis.
16    pub min_x: X,
17    /// The maximum visible bound along the *x* axis.
18    pub max_x: X,
19    /// The minimum visible bound along the *y* axis.
20    pub min_y: Y,
21    /// The maximum visible bound along the *y* axis.
22    pub max_y: Y,
23    /// An offset for all vertical lines distributed across the *x* axis.
24    pub x_offset: Option<X>,
25    /// An offset for all horizontal lines distributed across the *y* axis.
26    pub y_offset: Option<Y>,
27    /// An iterator yielding each sequence of lines to be distributed across the grid.
28    pub lines: I,
29}
30
31/// Unique styling parameters for the `Grid` widget.
32#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
33pub struct Style {
34    /// The color of the grid lines.
35    #[conrod(default = "theme.shape_color")]
36    pub color: Option<Color>,
37    /// The thickness of the grid lines.
38    #[conrod(default = "1.0")]
39    pub thickness: Option<Scalar>,
40}
41
42/// A series of lines distributed across an axis.
43#[derive(Copy, Clone, Debug)]
44pub struct Lines<T> {
45    /// The distance that separates each line.
46    pub step: T,
47    /// An optional offset for the lines along they're axis.
48    pub offset: Option<T>,
49    /// The thickness of each of the lines drawn.
50    ///
51    /// If `None`, the `thickness` specified within the `Style` is used.
52    pub thickness: Option<Scalar>,
53    /// The color of each of the lines drawn.
54    ///
55    /// If `None`, the `color` specified within the `Style` is used.
56    pub color: Option<Color>,
57}
58
59/// A series of lines distributed over an axis.
60#[derive(Copy, Clone, Debug)]
61pub enum Axis<X, Y> {
62    /// Vertical lines that are spread across the *x* axis.
63    X(Lines<X>),
64    /// Horizontal lines that are spread across the *y* axis.
65    Y(Lines<Y>),
66}
67
68widget_ids! {
69    struct Ids {
70        lines[],
71    }
72}
73
74/// Unique state for the `Grid` retained between updates.
75pub struct State {
76    ids: Ids,
77}
78
79impl<T> Lines<T> {
80    /// Begin building a new set of lines for the grid `step` distance apart.
81    ///
82    /// Lines with a `step` that equates to `0.0` or less will not be drawn.
83    pub fn step(step: T) -> Self {
84        Lines {
85            step: step,
86            offset: None,
87            thickness: None,
88            color: None,
89        }
90    }
91
92    /// Specify an offset for the grid.
93    ///
94    /// Offsets that are greater than the `step` size will be wrapped around the `step` size.
95    pub fn offset(mut self, offset: T) -> Self {
96        self.offset = Some(offset);
97        self
98    }
99
100    /// Specify a unique thickness for these lines.
101    pub fn thickness(mut self, thickness: Scalar) -> Self {
102        self.thickness = Some(thickness);
103        self
104    }
105
106    /// Use the specified color to uniquely color the this set of lines.
107    pub fn color(mut self, color: Color) -> Self {
108        self.color = Some(color);
109        self
110    }
111
112    /// Move the lines over the X axis.
113    pub fn x<Y>(self) -> Axis<T, Y> {
114        Axis::X(self)
115    }
116
117    /// Move the lines over the Y axis.
118    pub fn y<X>(self) -> Axis<X, T> {
119        Axis::Y(self)
120    }
121}
122
123impl<X, Y, I> Grid<X, Y, I> {
124    /// Begin building a new `PlotPath` widget instance.
125    ///
126    /// The first four arguments represent the visible range along both axes.
127    ///
128    /// The final argument is an iterator yielding `Lines` across either `Axis`. The given lines
129    /// will be drawn in the order that they're given.
130    pub fn new(min_x: X, max_x: X, min_y: Y, max_y: Y, lines: I) -> Grid<X, Y, I::IntoIter>
131    where
132        X: Into<Scalar>,
133        Y: Into<Scalar>,
134        I: IntoIterator<Item = Axis<X, Y>>,
135    {
136        Grid {
137            common: CommonBuilder::default(),
138            style: Style::default(),
139            min_x: min_x,
140            max_x: max_x,
141            min_y: min_y,
142            max_y: max_y,
143            x_offset: None,
144            y_offset: None,
145            lines: lines.into_iter(),
146        }
147    }
148
149    /// Specify an offset for all vertical lines placed along the X axis.
150    pub fn x_offset(mut self, x: X) -> Self {
151        self.x_offset = Some(x);
152        self
153    }
154
155    /// Specify an offset for all horizontal lines placed along the Y axis.
156    pub fn y_offset(mut self, y: Y) -> Self {
157        self.y_offset = Some(y);
158        self
159    }
160}
161
162impl<X, Y, I> Widget for Grid<X, Y, I>
163where
164    X: Into<Scalar>,
165    Y: Into<Scalar>,
166    I: Iterator<Item = Axis<X, Y>>,
167{
168    type State = State;
169    type Style = Style;
170    type Event = ();
171
172    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
173        State {
174            ids: Ids::new(id_gen),
175        }
176    }
177
178    fn style(&self) -> Self::Style {
179        self.style.clone()
180    }
181
182    /// Update the state of the PlotPath.
183    fn update(self, args: UpdateArgs<Self>) -> Self::Event {
184        let UpdateArgs {
185            id,
186            state,
187            style,
188            rect,
189            ui,
190            ..
191        } = args;
192        let Grid {
193            min_x,
194            max_x,
195            min_y,
196            max_y,
197            x_offset,
198            y_offset,
199            lines,
200            ..
201        } = self;
202
203        let min_x_f: Scalar = min_x.into();
204        let max_x_f: Scalar = max_x.into();
205        let len_x_f = max_x_f - min_x_f;
206        let min_y_f: Scalar = min_y.into();
207        let max_y_f: Scalar = max_y.into();
208        let len_y_f = max_y_f - min_y_f;
209
210        let x_to_scalar_len = |x: X| {
211            let x_f: Scalar = x.into();
212            map_range(x_f, 0.0, len_x_f, 0.0, rect.x.len())
213        };
214
215        let y_to_scalar_len = |y: Y| {
216            let y_f: Scalar = y.into();
217            map_range(y_f, 0.0, len_y_f, 0.0, rect.y.len())
218        };
219
220        let color = style.color(&ui.theme);
221        let thickness = style.thickness(&ui.theme);
222        let x_offset_f = x_offset.map(&x_to_scalar_len).unwrap_or(0.0);
223        let y_offset_f = y_offset.map(&y_to_scalar_len).unwrap_or(0.0);
224        let mut line_num = 0;
225
226        let x_line = |x: Scalar| -> (Point, Point) {
227            let a = [x, rect.y.start];
228            let b = [x, rect.y.end];
229            (a, b)
230        };
231
232        let y_line = |y: Scalar| -> (Point, Point) {
233            let a = [rect.x.start, y];
234            let b = [rect.x.end, y];
235            (a, b)
236        };
237
238        macro_rules! draw_lines {
239            ($lines:ident, $offset:expr, $to_scalar:ident, $step_range:expr, $line_points:ident) => {{
240                let offset = $offset + $lines.offset.map(&$to_scalar).unwrap_or(0.0);
241                let thickness = $lines.thickness.unwrap_or(thickness);
242                let color = $lines.color.unwrap_or(color);
243                let step = $to_scalar($lines.step);
244                if step == 0.0 {
245                    continue;
246                }
247                let mut pos = $step_range.start + offset % step;
248                while $step_range.is_over(pos) {
249                    // The start and end of the line.
250                    let (a, b) = $line_points(pos);
251
252                    // The unique identifier for this line.
253                    if line_num >= state.ids.lines.len() {
254                        state.update(|state| {
255                            state
256                                .ids
257                                .lines
258                                .resize(line_num + 1, &mut ui.widget_id_generator());
259                        });
260                    }
261                    let line_id = state.ids.lines[line_num];
262
263                    // Draw the line.
264                    widget::Line::abs(a, b)
265                        .color(color)
266                        .thickness(thickness)
267                        .parent(id)
268                        .graphics_for(id)
269                        .set(line_id, ui);
270
271                    pos += step;
272                    line_num += 1;
273                }
274            }};
275        }
276
277        for axis in lines {
278            match axis {
279                Axis::X(lines) => draw_lines!(lines, x_offset_f, x_to_scalar_len, rect.x, x_line),
280                Axis::Y(lines) => draw_lines!(lines, y_offset_f, y_to_scalar_len, rect.y, y_line),
281            }
282        }
283    }
284}
285
286impl<X, Y, I> Colorable for Grid<X, Y, I> {
287    builder_method!(color { style.color = Some(Color) });
288}