embedded_charts/grid/
builder.rs

1//! Grid builder implementations for fluent configuration.
2
3#[cfg(all(feature = "no_std", not(feature = "std")))]
4extern crate alloc;
5
6#[cfg(all(feature = "no_std", not(feature = "std")))]
7use alloc::boxed::Box;
8
9#[cfg(not(all(feature = "no_std", not(feature = "std"))))]
10use std::boxed::Box;
11
12use crate::grid::{
13    style::{GridStyle, GridVisibility, MajorGridStyle, MinorGridStyle},
14    traits::{Grid, GridOrientation},
15    types::{CustomGrid, GridSpacing, LinearGrid, TickBasedGrid},
16    GridContainer, GridSystem,
17};
18use embedded_graphics::prelude::*;
19
20/// Main grid builder for configuring grid systems
21#[derive(Debug)]
22pub struct GridBuilder<C: PixelColor> {
23    horizontal_grid: Option<GridContainer<C>>,
24    vertical_grid: Option<GridContainer<C>>,
25    style: GridStyle<C>,
26    enabled: bool,
27}
28
29impl<C: PixelColor + 'static> GridBuilder<C>
30where
31    C: From<embedded_graphics::pixelcolor::Rgb565>,
32{
33    /// Create a new grid builder
34    pub fn new() -> Self {
35        Self {
36            horizontal_grid: None,
37            vertical_grid: None,
38            style: GridStyle::default(),
39            enabled: true,
40        }
41    }
42
43    /// Set a linear horizontal grid
44    pub fn horizontal_linear(mut self, spacing: GridSpacing) -> Self {
45        self.horizontal_grid = Some(GridContainer::Linear(LinearGrid::horizontal(spacing)));
46        self
47    }
48
49    /// Set a linear vertical grid
50    pub fn vertical_linear(mut self, spacing: GridSpacing) -> Self {
51        self.vertical_grid = Some(GridContainer::Linear(LinearGrid::vertical(spacing)));
52        self
53    }
54
55    /// Set a tick-based horizontal grid for f32 values
56    pub fn horizontal_tick_based_f32(mut self) -> Self {
57        self.horizontal_grid = Some(GridContainer::TickBasedF32(
58            TickBasedGrid::<f32, C>::horizontal(),
59        ));
60        self
61    }
62
63    /// Set a tick-based vertical grid for f32 values
64    pub fn vertical_tick_based_f32(mut self) -> Self {
65        self.vertical_grid = Some(GridContainer::TickBasedF32(
66            TickBasedGrid::<f32, C>::vertical(),
67        ));
68        self
69    }
70
71    /// Set a tick-based horizontal grid for i32 values
72    pub fn horizontal_tick_based_i32(mut self) -> Self {
73        self.horizontal_grid = Some(GridContainer::TickBasedI32(
74            TickBasedGrid::<i32, C>::horizontal(),
75        ));
76        self
77    }
78
79    /// Set a tick-based vertical grid for i32 values
80    pub fn vertical_tick_based_i32(mut self) -> Self {
81        self.vertical_grid = Some(GridContainer::TickBasedI32(
82            TickBasedGrid::<i32, C>::vertical(),
83        ));
84        self
85    }
86
87    /// Set a custom horizontal grid
88    pub fn horizontal_custom(mut self, positions: &[i32]) -> Self {
89        let mut grid = CustomGrid::horizontal();
90        grid.add_lines(positions);
91        self.horizontal_grid = Some(GridContainer::Custom(Box::new(grid)));
92        self
93    }
94
95    /// Set a custom vertical grid
96    pub fn vertical_custom(mut self, positions: &[i32]) -> Self {
97        let mut grid = CustomGrid::vertical();
98        grid.add_lines(positions);
99        self.vertical_grid = Some(GridContainer::Custom(Box::new(grid)));
100        self
101    }
102
103    /// Set the overall grid style
104    pub fn style(mut self, style: GridStyle<C>) -> Self {
105        self.style = style;
106        self
107    }
108
109    /// Use professional grid styling
110    pub fn professional(mut self) -> Self {
111        self.style = GridStyle::professional();
112        self
113    }
114
115    /// Use minimal grid styling
116    pub fn minimal(mut self) -> Self {
117        self.style = GridStyle::minimal();
118        self
119    }
120
121    /// Use dashed grid styling
122    pub fn dashed(mut self) -> Self {
123        self.style = GridStyle::dashed();
124        self
125    }
126
127    /// Set grid visibility
128    pub fn visibility(mut self, visibility: GridVisibility) -> Self {
129        self.style.visibility = visibility;
130        self
131    }
132
133    /// Enable or disable the grid
134    pub fn enabled(mut self, enabled: bool) -> Self {
135        self.enabled = enabled;
136        self
137    }
138
139    /// Set grid opacity
140    pub fn opacity(mut self, opacity: f32) -> Self {
141        self.style.opacity = opacity.clamp(0.0, 1.0);
142        self
143    }
144
145    /// Configure major grid lines
146    pub fn major_grid(mut self, style: MajorGridStyle<C>) -> Self {
147        self.style.major = style;
148        self
149    }
150
151    /// Configure minor grid lines
152    pub fn minor_grid(mut self, style: MinorGridStyle<C>) -> Self {
153        self.style.minor = style;
154        self
155    }
156
157    /// Build the grid system
158    pub fn build(self) -> GridSystem<C> {
159        let mut grid_system = GridSystem::new();
160        grid_system.style = self.style;
161        grid_system.enabled = self.enabled;
162
163        if let Some(horizontal) = self.horizontal_grid {
164            grid_system.horizontal = Some(horizontal);
165        }
166
167        if let Some(vertical) = self.vertical_grid {
168            grid_system.vertical = Some(vertical);
169        }
170
171        grid_system
172    }
173}
174
175impl<C: PixelColor + 'static> Default for GridBuilder<C>
176where
177    C: From<embedded_graphics::pixelcolor::Rgb565>,
178{
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184/// Builder for linear grids
185#[derive(Debug)]
186pub struct LinearGridBuilder<C: PixelColor> {
187    orientation: GridOrientation,
188    spacing: GridSpacing,
189    style: GridStyle<C>,
190    visible: bool,
191}
192
193impl<C: PixelColor> LinearGridBuilder<C>
194where
195    C: From<embedded_graphics::pixelcolor::Rgb565>,
196{
197    /// Create a new linear grid builder
198    pub fn new(orientation: GridOrientation) -> Self {
199        Self {
200            orientation,
201            spacing: GridSpacing::Auto,
202            style: GridStyle::default(),
203            visible: true,
204        }
205    }
206
207    /// Create a horizontal linear grid builder
208    pub fn horizontal() -> Self {
209        Self::new(GridOrientation::Horizontal)
210    }
211
212    /// Create a vertical linear grid builder
213    pub fn vertical() -> Self {
214        Self::new(GridOrientation::Vertical)
215    }
216
217    /// Set the grid spacing
218    pub fn spacing(mut self, spacing: GridSpacing) -> Self {
219        self.spacing = spacing;
220        self
221    }
222
223    /// Set spacing in pixels
224    pub fn spacing_pixels(mut self, pixels: u32) -> Self {
225        self.spacing = GridSpacing::Pixels(pixels);
226        self
227    }
228
229    /// Set spacing in data units
230    pub fn spacing_data_units(mut self, units: f32) -> Self {
231        self.spacing = GridSpacing::DataUnits(units);
232        self
233    }
234
235    /// Use automatic spacing
236    pub fn spacing_auto(mut self) -> Self {
237        self.spacing = GridSpacing::Auto;
238        self
239    }
240
241    /// Set the grid style
242    pub fn style(mut self, style: GridStyle<C>) -> Self {
243        self.style = style;
244        self
245    }
246
247    /// Set grid visibility
248    pub fn visible(mut self, visible: bool) -> Self {
249        self.visible = visible;
250        self
251    }
252
253    /// Build the linear grid
254    pub fn build(self) -> LinearGrid<C> {
255        LinearGrid::new(self.orientation, self.spacing)
256            .with_style(self.style)
257            .with_visibility(self.visible)
258    }
259}
260
261/// Builder for tick-based grids
262#[derive(Debug)]
263pub struct TickBasedGridBuilder<T, C>
264where
265    T: Copy + PartialOrd + core::fmt::Display,
266    C: PixelColor,
267{
268    orientation: GridOrientation,
269    style: GridStyle<C>,
270    visible: bool,
271    major_ticks_only: bool,
272    _phantom: core::marker::PhantomData<T>,
273}
274
275impl<T, C> TickBasedGridBuilder<T, C>
276where
277    T: Copy + PartialOrd + core::fmt::Display,
278    C: PixelColor,
279{
280    /// Create a new tick-based grid builder
281    pub fn new(orientation: GridOrientation) -> Self
282    where
283        C: From<embedded_graphics::pixelcolor::Rgb565>,
284    {
285        Self {
286            orientation,
287            style: GridStyle::default(),
288            visible: true,
289            major_ticks_only: false,
290            _phantom: core::marker::PhantomData,
291        }
292    }
293
294    /// Create a horizontal tick-based grid builder
295    pub fn horizontal() -> Self
296    where
297        C: From<embedded_graphics::pixelcolor::Rgb565>,
298    {
299        Self::new(GridOrientation::Horizontal)
300    }
301
302    /// Create a vertical tick-based grid builder
303    pub fn vertical() -> Self
304    where
305        C: From<embedded_graphics::pixelcolor::Rgb565>,
306    {
307        Self::new(GridOrientation::Vertical)
308    }
309
310    /// Set the grid style
311    pub fn style(mut self, style: GridStyle<C>) -> Self {
312        self.style = style;
313        self
314    }
315
316    /// Set grid visibility
317    pub fn visible(mut self, visible: bool) -> Self {
318        self.visible = visible;
319        self
320    }
321
322    /// Set whether to show only major tick grid lines
323    pub fn major_ticks_only(mut self, major_only: bool) -> Self {
324        self.major_ticks_only = major_only;
325        self
326    }
327
328    /// Build the tick-based grid
329    pub fn build(self) -> TickBasedGrid<T, C>
330    where
331        C: From<embedded_graphics::pixelcolor::Rgb565>,
332    {
333        TickBasedGrid::new(self.orientation)
334            .with_style(self.style)
335            .with_major_ticks_only(self.major_ticks_only)
336    }
337}
338
339/// Builder for custom grids
340#[derive(Debug)]
341pub struct CustomGridBuilder<C: PixelColor> {
342    orientation: GridOrientation,
343    positions: heapless::Vec<i32, 64>,
344    style: GridStyle<C>,
345    visible: bool,
346}
347
348impl<C: PixelColor + 'static> CustomGridBuilder<C>
349where
350    C: From<embedded_graphics::pixelcolor::Rgb565>,
351{
352    /// Create a new custom grid builder
353    pub fn new(orientation: GridOrientation) -> Self {
354        Self {
355            orientation,
356            positions: heapless::Vec::new(),
357            style: GridStyle::default(),
358            visible: true,
359        }
360    }
361
362    /// Create a horizontal custom grid builder
363    pub fn horizontal() -> Self {
364        Self::new(GridOrientation::Horizontal)
365    }
366
367    /// Create a vertical custom grid builder
368    pub fn vertical() -> Self {
369        Self::new(GridOrientation::Vertical)
370    }
371
372    /// Add a grid line position
373    pub fn add_line(mut self, position: i32) -> Self {
374        let _ = self.positions.push(position);
375        self
376    }
377
378    /// Add multiple grid line positions
379    pub fn add_lines(mut self, positions: &[i32]) -> Self {
380        for &pos in positions {
381            let _ = self.positions.push(pos);
382        }
383        self
384    }
385
386    /// Set evenly spaced lines
387    pub fn evenly_spaced(mut self, start: i32, end: i32, count: usize) -> Self {
388        if count > 1 {
389            let step = (end - start) / (count - 1) as i32;
390            for i in 0..count {
391                let pos = start + i as i32 * step;
392                let _ = self.positions.push(pos);
393            }
394        }
395        self
396    }
397
398    /// Set the grid style
399    pub fn style(mut self, style: GridStyle<C>) -> Self {
400        self.style = style;
401        self
402    }
403
404    /// Set grid visibility
405    pub fn visible(mut self, visible: bool) -> Self {
406        self.visible = visible;
407        self
408    }
409
410    /// Build the custom grid
411    pub fn build(self) -> CustomGrid<C> {
412        let mut grid = CustomGrid::new(self.orientation).with_style(self.style);
413        grid.set_visible(self.visible);
414
415        for &pos in self.positions.iter() {
416            let _ = grid.add_line(pos);
417        }
418
419        grid
420    }
421}
422
423/// Convenience functions for quick grid creation
424pub mod presets {
425    use super::*;
426    use embedded_graphics::pixelcolor::Rgb565;
427
428    /// Create a professional grid system with both horizontal and vertical grids
429    pub fn professional_grid() -> GridSystem<Rgb565> {
430        GridBuilder::new()
431            .horizontal_linear(GridSpacing::Auto)
432            .vertical_linear(GridSpacing::Auto)
433            .professional()
434            .build()
435    }
436
437    /// Create a minimal grid system with major lines only
438    pub fn minimal_grid() -> GridSystem<Rgb565> {
439        GridBuilder::new()
440            .horizontal_linear(GridSpacing::Auto)
441            .vertical_linear(GridSpacing::Auto)
442            .minimal()
443            .build()
444    }
445
446    /// Create a dashed grid system
447    pub fn dashed_grid() -> GridSystem<Rgb565> {
448        GridBuilder::new()
449            .horizontal_linear(GridSpacing::Auto)
450            .vertical_linear(GridSpacing::Auto)
451            .dashed()
452            .build()
453    }
454
455    /// Create a tick-aligned grid system for f32 values
456    pub fn tick_aligned_grid_f32() -> GridSystem<Rgb565> {
457        GridBuilder::new()
458            .horizontal_tick_based_f32()
459            .vertical_tick_based_f32()
460            .professional()
461            .build()
462    }
463
464    /// Create a tick-aligned grid system for i32 values
465    pub fn tick_aligned_grid_i32() -> GridSystem<Rgb565> {
466        GridBuilder::new()
467            .horizontal_tick_based_i32()
468            .vertical_tick_based_i32()
469            .professional()
470            .build()
471    }
472
473    /// Create a horizontal-only grid
474    pub fn horizontal_only_grid() -> GridSystem<Rgb565> {
475        GridBuilder::new()
476            .horizontal_linear(GridSpacing::Auto)
477            .visibility(GridVisibility::horizontal_only())
478            .professional()
479            .build()
480    }
481
482    /// Create a vertical-only grid
483    pub fn vertical_only_grid() -> GridSystem<Rgb565> {
484        GridBuilder::new()
485            .vertical_linear(GridSpacing::Auto)
486            .visibility(GridVisibility::vertical_only())
487            .professional()
488            .build()
489    }
490}
491
492#[cfg(test)]
493mod tests {
494    use super::*;
495    use embedded_graphics::pixelcolor::Rgb565;
496
497    #[test]
498    fn test_grid_builder() {
499        let grid = GridBuilder::<Rgb565>::new()
500            .horizontal_linear(GridSpacing::Pixels(20))
501            .vertical_linear(GridSpacing::Pixels(30))
502            .professional()
503            .build();
504
505        assert!(grid.is_enabled());
506        assert!(grid.horizontal.is_some());
507        assert!(grid.vertical.is_some());
508    }
509
510    #[test]
511    fn test_linear_grid_builder() {
512        let grid = LinearGridBuilder::<Rgb565>::horizontal()
513            .spacing_pixels(25)
514            .visible(true)
515            .build();
516
517        assert_eq!(grid.orientation(), GridOrientation::Horizontal);
518        assert!(grid.is_visible());
519    }
520
521    #[test]
522    fn test_tick_based_grid_builder() {
523        let grid = TickBasedGridBuilder::<f32, Rgb565>::vertical()
524            .major_ticks_only(true)
525            .build();
526
527        assert_eq!(grid.orientation(), GridOrientation::Vertical);
528        assert!(grid.is_major_ticks_only());
529    }
530
531    #[test]
532    fn test_custom_grid_builder() {
533        let grid = CustomGridBuilder::<Rgb565>::horizontal()
534            .add_line(100)
535            .add_line(200)
536            .add_line(300)
537            .build();
538
539        assert_eq!(grid.orientation(), GridOrientation::Horizontal);
540        let positions = grid.calculate_positions(embedded_graphics::primitives::Rectangle::new(
541            embedded_graphics::prelude::Point::zero(),
542            embedded_graphics::prelude::Size::new(400, 300),
543        ));
544        assert_eq!(positions.len(), 3);
545    }
546
547    #[test]
548    fn test_preset_grids() {
549        let professional = presets::professional_grid();
550        assert!(professional.is_enabled());
551
552        let minimal = presets::minimal_grid();
553        assert!(minimal.is_enabled());
554
555        let dashed = presets::dashed_grid();
556        assert!(dashed.is_enabled());
557    }
558}