embedded_charts/dashboard/
layout.rs

1//! Layout strategies for dashboard arrangement
2
3use super::{GridLayout, GridPosition, MAX_DASHBOARD_CHARTS};
4use crate::error::ChartResult;
5use embedded_graphics::primitives::Rectangle;
6use heapless::Vec;
7
8/// Layout strategy for arranging charts in a dashboard
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum DashboardLayout {
11    /// Grid-based layout with fixed rows and columns
12    Grid(GridLayout),
13    /// Flexible layout (future implementation)
14    Flexible,
15}
16
17impl DashboardLayout {
18    /// Calculate viewports for all panels based on the layout strategy
19    pub fn calculate_viewports(
20        &self,
21        total_viewport: Rectangle,
22        panel_count: usize,
23        spacing: u32,
24    ) -> ChartResult<Vec<Rectangle, MAX_DASHBOARD_CHARTS>> {
25        match self {
26            DashboardLayout::Grid(grid) => {
27                // For now, auto-arrange panels in grid order
28                let mut positions: Vec<GridPosition, MAX_DASHBOARD_CHARTS> = Vec::new();
29                let mut index = 0;
30
31                'outer: for row in 0..grid.rows {
32                    for col in 0..grid.cols {
33                        if index >= panel_count {
34                            break 'outer;
35                        }
36                        positions
37                            .push(GridPosition::new(row, col))
38                            .map_err(|_| crate::error::ChartError::MemoryFull)?;
39                        index += 1;
40                    }
41                }
42
43                grid.calculate_viewports(total_viewport, &positions, spacing)
44            }
45            DashboardLayout::Flexible => {
46                // Future implementation
47                Err(crate::error::ChartError::ConfigurationError)
48            }
49        }
50    }
51}
52
53/// Layout presets for common dashboard configurations
54pub enum LayoutPreset {
55    /// Single chart (1x1)
56    Single,
57    /// Side by side (1x2)
58    SideBySide,
59    /// Stacked vertically (2x1)
60    Stacked,
61    /// Four quadrants (2x2)
62    Quadrants,
63    /// Three columns (1x3)
64    ThreeColumns,
65    /// Three rows (3x1)
66    ThreeRows,
67    /// 3x3 grid
68    Grid3x3,
69    /// 4x4 grid
70    Grid4x4,
71}
72
73impl LayoutPreset {
74    /// Convert preset to dashboard layout
75    pub fn to_layout(self) -> DashboardLayout {
76        match self {
77            LayoutPreset::Single => DashboardLayout::Grid(GridLayout::new(1, 1)),
78            LayoutPreset::SideBySide => DashboardLayout::Grid(GridLayout::new(1, 2)),
79            LayoutPreset::Stacked => DashboardLayout::Grid(GridLayout::new(2, 1)),
80            LayoutPreset::Quadrants => DashboardLayout::Grid(GridLayout::new(2, 2)),
81            LayoutPreset::ThreeColumns => DashboardLayout::Grid(GridLayout::new(1, 3)),
82            LayoutPreset::ThreeRows => DashboardLayout::Grid(GridLayout::new(3, 1)),
83            LayoutPreset::Grid3x3 => DashboardLayout::Grid(GridLayout::new(3, 3)),
84            LayoutPreset::Grid4x4 => DashboardLayout::Grid(GridLayout::new(4, 4)),
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use embedded_graphics::prelude::*;
93
94    #[test]
95    fn test_layout_preset_conversion() {
96        let layout = LayoutPreset::Quadrants.to_layout();
97        match layout {
98            DashboardLayout::Grid(grid) => {
99                assert_eq!(grid.rows, 2);
100                assert_eq!(grid.cols, 2);
101            }
102            _ => panic!("Expected grid layout"),
103        }
104    }
105
106    #[test]
107    fn test_dashboard_layout_calculate_viewports() {
108        let layout = DashboardLayout::Grid(GridLayout::new(2, 2));
109        let viewport = Rectangle::new(Point::new(0, 0), Size::new(200, 200));
110
111        let viewports = layout.calculate_viewports(viewport, 3, 10).unwrap();
112        assert_eq!(viewports.len(), 3);
113
114        // First viewport should be top-left
115        assert_eq!(viewports[0].top_left, Point::new(0, 0));
116        assert_eq!(viewports[0].size, Size::new(95, 95));
117
118        // Second viewport should be top-right
119        assert_eq!(viewports[1].top_left, Point::new(105, 0));
120        assert_eq!(viewports[1].size, Size::new(95, 95));
121
122        // Third viewport should be bottom-left
123        assert_eq!(viewports[2].top_left, Point::new(0, 105));
124        assert_eq!(viewports[2].size, Size::new(95, 95));
125    }
126}