Skip to main content

egui_charts/ui_kit/form/
grid.rs

1//! Grid layout for form labels and controls.
2//!
3//! Use [`FormGrid`] for rows with shared spacing; close each row with
4//! [`egui::Ui::end_row`].
5
6use crate::theme::ThemeExt;
7use egui::Ui;
8
9/// A pre-configured 2-column grid for form layouts.
10///
11/// Default: 2 columns, `[lg, md]` spacing (horizontal, vertical).
12pub struct FormGrid {
13    id: String,
14    columns: usize,
15    spacing: Option<[f32; 2]>,
16}
17
18impl FormGrid {
19    /// Create a two-column form grid with a stable egui identity.
20    pub fn new(id: impl Into<String>) -> Self {
21        Self {
22            id: id.into(),
23            columns: 2,
24            spacing: None,
25        }
26    }
27
28    /// Override the number of columns (default: 2).
29    #[must_use]
30    pub fn columns(mut self, n: usize) -> Self {
31        self.columns = n;
32        self
33    }
34
35    /// Override spacing: `[horizontal, vertical]`.
36    #[must_use]
37    pub fn spacing(mut self, h: f32, v: f32) -> Self {
38        self.spacing = Some([h, v]);
39        self
40    }
41
42    /// Show the grid. The closure should use `ui.label(...); ui.add(...); ui.end_row();` rows.
43    pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
44        let theme = ui.active_theme();
45        egui::Grid::new(self.id)
46            .num_columns(self.columns)
47            .spacing(self.spacing.unwrap_or([theme.spacing.lg, theme.spacing.md]))
48            .show(ui, add_contents);
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::theme::{Theme, apply_to_egui};
56
57    #[test]
58    fn spacing_follows_the_theme_unless_the_host_sets_it() {
59        let ctx = egui::Context::default();
60        for custom in [false, true] {
61            let mut theme = Theme::light();
62            theme.spacing.lg = 21.0;
63            theme.spacing.md = 17.0;
64            apply_to_egui(&ctx, &theme);
65            let expected = if custom { [3.0, 5.0] } else { [21.0, 17.0] };
66            let mut cells = [egui::Rect::NOTHING; 4];
67            for _ in 0..3 {
68                ctx.run_ui(Default::default(), |ui| {
69                    let grid = FormGrid::new(format!("spacing_{custom}"));
70                    let grid = if custom { grid.spacing(3.0, 5.0) } else { grid };
71                    grid.show(ui, |ui| {
72                        for (at, cell) in cells.iter_mut().enumerate() {
73                            *cell = ui
74                                .allocate_exact_size(egui::vec2(80.0, 20.0), egui::Sense::hover())
75                                .0;
76                            if at % 2 == 1 {
77                                ui.end_row();
78                            }
79                        }
80                    });
81                })
82                .drop_without_applying_deltas();
83            }
84            assert_eq!(cells[1].left() - cells[0].right(), expected[0]);
85            assert_eq!(cells[2].top() - cells[0].bottom(), expected[1]);
86        }
87    }
88}