embedded_charts/dashboard/
grid.rs1use embedded_graphics::{prelude::*, primitives::Rectangle};
4use heapless::Vec;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct GridPosition {
9 pub row: u8,
11 pub col: u8,
13 pub row_span: u8,
15 pub col_span: u8,
17}
18
19impl GridPosition {
20 pub fn new(row: u8, col: u8) -> Self {
22 Self {
23 row,
24 col,
25 row_span: 1,
26 col_span: 1,
27 }
28 }
29
30 pub fn with_span(row: u8, col: u8, row_span: u8, col_span: u8) -> Self {
32 Self {
33 row,
34 col,
35 row_span: row_span.max(1),
36 col_span: col_span.max(1),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct GridLayout {
44 pub rows: u8,
46 pub cols: u8,
48}
49
50impl GridLayout {
51 pub fn new(rows: u8, cols: u8) -> Self {
53 Self {
54 rows: rows.max(1),
55 cols: cols.max(1),
56 }
57 }
58
59 pub fn calculate_cell_viewport(
61 &self,
62 total_viewport: Rectangle,
63 position: GridPosition,
64 spacing: u32,
65 ) -> Rectangle {
66 let total_width = total_viewport.size.width;
67 let total_height = total_viewport.size.height;
68
69 let h_spacing = spacing * (self.cols as u32 - 1);
71 let v_spacing = spacing * (self.rows as u32 - 1);
72
73 let cell_width = (total_width.saturating_sub(h_spacing)) / self.cols as u32;
75 let cell_height = (total_height.saturating_sub(v_spacing)) / self.rows as u32;
76
77 let x = total_viewport.top_left.x
79 + (position.col as i32 * (cell_width as i32 + spacing as i32));
80 let y = total_viewport.top_left.y
81 + (position.row as i32 * (cell_height as i32 + spacing as i32));
82
83 let width = (cell_width * position.col_span as u32)
85 + (spacing * position.col_span.saturating_sub(1) as u32);
86 let height = (cell_height * position.row_span as u32)
87 + (spacing * position.row_span.saturating_sub(1) as u32);
88
89 Rectangle::new(Point::new(x, y), Size::new(width, height))
90 }
91
92 pub fn calculate_viewports<const N: usize>(
94 &self,
95 total_viewport: Rectangle,
96 positions: &[GridPosition],
97 spacing: u32,
98 ) -> crate::error::ChartResult<Vec<Rectangle, N>> {
99 let mut viewports = Vec::new();
100
101 for position in positions {
102 let viewport = self.calculate_cell_viewport(total_viewport, *position, spacing);
103 viewports
104 .push(viewport)
105 .map_err(|_| crate::error::ChartError::MemoryFull)?;
106 }
107
108 Ok(viewports)
109 }
110}
111
112impl Default for GridLayout {
113 fn default() -> Self {
114 Self::new(2, 2)
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_grid_position() {
124 let pos = GridPosition::new(1, 2);
125 assert_eq!(pos.row, 1);
126 assert_eq!(pos.col, 2);
127 assert_eq!(pos.row_span, 1);
128 assert_eq!(pos.col_span, 1);
129
130 let span_pos = GridPosition::with_span(0, 0, 2, 3);
131 assert_eq!(span_pos.row_span, 2);
132 assert_eq!(span_pos.col_span, 3);
133 }
134
135 #[test]
136 fn test_grid_layout_2x2() {
137 let layout = GridLayout::new(2, 2);
138 let viewport = Rectangle::new(Point::new(0, 0), Size::new(200, 200));
139
140 let cell = layout.calculate_cell_viewport(
142 viewport,
143 GridPosition::new(0, 0),
144 10, );
146 assert_eq!(cell.top_left, Point::new(0, 0));
147 assert_eq!(cell.size, Size::new(95, 95)); let cell = layout.calculate_cell_viewport(viewport, GridPosition::new(1, 1), 10);
151 assert_eq!(cell.top_left, Point::new(105, 105)); assert_eq!(cell.size, Size::new(95, 95));
153 }
154
155 #[test]
156 fn test_grid_layout_with_span() {
157 let layout = GridLayout::new(3, 3);
158 let viewport = Rectangle::new(Point::new(0, 0), Size::new(320, 320));
159
160 let cell =
162 layout.calculate_cell_viewport(viewport, GridPosition::with_span(0, 0, 2, 2), 10);
163 assert_eq!(cell.top_left, Point::new(0, 0));
164 assert_eq!(cell.size, Size::new(210, 210)); }
166}