Skip to main content

egui_charts/ui_kit/form/
mod.rs

1//! Form layout components.
2//!
3//! Provides a two-column layout for label + widget form rows: a fixed-width
4//! label column and a left-aligned widget column.
5//!
6//! # Example
7//! ```
8//! # use egui_charts::ui_kit::form::FormRow;
9//! # egui::__run_test_ui(|ui| {
10//! # let mut current = "dark";
11//! FormRow::new("Theme")
12//!     .description("Color scheme for the application")
13//!     .show(ui, |ui| {
14//!         egui::ComboBox::from_id_salt("theme")
15//!             .selected_text(current)
16//!             .show_ui(ui, |ui| {
17//!                 for (name, _) in egui_charts::theme::presets() {
18//!                     ui.selectable_value(&mut current, name, name);
19//!                 }
20//!             });
21//!     });
22//! # });
23//! ```
24
25use crate::localization as l10n;
26use crate::theme::ThemeExt;
27pub mod grid;
28
29use crate::tokens::DESIGN_TOKENS;
30use egui::{Align, Layout, RichText, Ui, vec2};
31
32pub use grid::FormGrid;
33
34/// A two-column form row: fixed-width label area + remainder widget area.
35pub struct FormRow<'a> {
36    label: &'a str,
37    description: Option<&'a str>,
38    label_width: f32,
39}
40
41impl<'a> FormRow<'a> {
42    /// Create a form row with the supplied label.
43    pub fn new(label: &'a str) -> Self {
44        Self {
45            label,
46            description: None,
47            label_width: DESIGN_TOKENS.sizing.settings_dialog.label_width,
48        }
49    }
50
51    /// Set explanatory text displayed with the form row.
52    pub fn description(mut self, desc: &'a str) -> Self {
53        self.description = Some(desc);
54        self
55    }
56
57    /// Set the label column width in egui logical points.
58    pub fn label_width(mut self, width: f32) -> Self {
59        self.label_width = width;
60        self
61    }
62
63    /// Show the form row with the given widget content.
64    ///
65    /// The row is as tall as its content, so consecutive rows stack. The label and the
66    /// widget are centred on one line; the description follows below them.
67    pub fn show(self, ui: &mut Ui, add_widget: impl FnOnce(&mut Ui)) {
68        let spacing = ui.spacing().item_spacing;
69        ui.spacing_mut().item_spacing.y = ui.active_theme().spacing.md;
70        ui.horizontal(|ui| {
71            ui.spacing_mut().item_spacing.x = ui.active_theme().spacing.lg;
72            let label = ui.allocate_ui_with_layout(
73                vec2(self.label_width, ui.spacing().interact_size.y),
74                Layout::left_to_right(Align::Center),
75                |ui| {
76                    ui.set_width(self.label_width);
77                    ui.label(l10n::text(ui.ctx(), self.label))
78                },
79            );
80            ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
81                ui.label_inputs_by(label.inner.id, add_widget);
82            });
83        });
84        if let Some(desc) = self.description {
85            ui.label(
86                RichText::new(l10n::text(ui.ctx(), desc))
87                    .size(ui.active_theme().text.small)
88                    .color(ui.visuals().weak_text_color()),
89            );
90        }
91        ui.spacing_mut().item_spacing = spacing;
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn rows_share_the_grid_control_edge_and_pitch() {
101        let ctx = egui::Context::default();
102        for frame in 0..3 {
103            ctx.run_ui(Default::default(), |ui| {
104                let width = 120.0;
105                let mut rows = Vec::new();
106                for control_width in [40.0, 100.0, 40.0] {
107                    FormRow::new("Label").label_width(width).show(ui, |ui| {
108                        rows.push(
109                            ui.allocate_exact_size(vec2(control_width, 24.0), egui::Sense::hover())
110                                .0,
111                        );
112                    });
113                }
114                let mut grid = Vec::new();
115                FormGrid::new("comparison").show(ui, |ui| {
116                    for _ in 0..2 {
117                        ui.add_sized(vec2(width, 24.0), egui::Label::new("Label"));
118                        grid.push(
119                            ui.allocate_exact_size(vec2(40.0, 24.0), egui::Sense::hover())
120                                .0,
121                        );
122                        ui.end_row();
123                    }
124                });
125                if frame < 2 {
126                    return;
127                }
128                assert_eq!(rows[0].left(), rows[1].left());
129                assert_eq!(rows[0].left(), grid[0].left());
130                assert_eq!(rows[1].top() - rows[0].top(), grid[1].top() - grid[0].top());
131            })
132            .drop_without_applying_deltas();
133        }
134    }
135}