Skip to main content

egui_macview/layout/
form.rs

1//! Authentic macOS AppKit Form Layout (`MacForm`).
2//!
3//! Compact wrapper around `egui::Grid` with AppKit token spacing,
4//! trailing-aligned labels (column 0), and leading-aligned controls (column 1).
5
6use crate::theme::MacTheme;
7use egui::{vec2, Align, IdSalt, Layout, Response, Ui, Vec2, WidgetText};
8
9/// Authentic macOS AppKit 2-column Form container.
10pub struct MacForm<'a> {
11    id_salt: IdSalt,
12    theme: Option<&'a MacTheme>,
13    spacing: Option<Vec2>,
14    min_col_width: Option<f32>,
15}
16
17impl<'a> MacForm<'a> {
18    /// Creates a new `MacForm` with the specified id salt.
19    pub fn new(id_salt: impl egui::AsIdSalt) -> Self {
20        Self {
21            id_salt: IdSalt::new(id_salt),
22            theme: None,
23            spacing: None,
24            min_col_width: None,
25        }
26    }
27
28    /// Sets the dynamic theme to derive AppKit token spacing.
29    pub fn theme(mut self, theme: &'a MacTheme) -> Self {
30        self.theme = Some(theme);
31        self
32    }
33
34    /// Overrides inter-item spacing between columns and rows in points.
35    pub fn spacing(mut self, spacing: impl Into<Vec2>) -> Self {
36        self.spacing = Some(spacing.into());
37        self
38    }
39
40    /// Overrides the minimum column width in points.
41    pub fn min_col_width(mut self, min_col_width: f32) -> Self {
42        self.min_col_width = Some(min_col_width);
43        self
44    }
45
46    /// Renders the form contents with trailing labels and leading controls.
47    pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut MacFormUi<'_>) -> R) -> R {
48        let spacing = self.spacing.unwrap_or_else(|| {
49            let s = self
50                .theme
51                .map(|t| t.stack_view().default_spacing.points())
52                .unwrap_or_else(|| {
53                    MacTheme::from_ctx(ui.ctx())
54                        .stack_view()
55                        .default_spacing
56                        .points()
57                });
58            vec2(s, s)
59        });
60
61        let mut result = None;
62        let mut grid = egui::Grid::new(self.id_salt)
63            .num_columns(2)
64            .spacing(spacing);
65        if let Some(min_w) = self.min_col_width {
66            grid = grid.min_col_width(min_w);
67        }
68        grid.show(ui, |grid_ui| {
69            let mut form_ui = MacFormUi { ui: grid_ui };
70            result = Some(add_contents(&mut form_ui));
71        });
72        result.expect("MacForm grid rendered")
73    }
74}
75
76/// Form builder context for adding rows with trailing labels and leading controls.
77pub struct MacFormUi<'a> {
78    /// Underlying egui `Ui` instance.
79    pub ui: &'a mut Ui,
80}
81
82impl<'a> MacFormUi<'a> {
83    /// Adds a row with a trailing-aligned label (col 0) and leading-aligned control (col 1).
84    pub fn row<R1, R2>(
85        &mut self,
86        label: impl FnOnce(&mut Ui) -> R1,
87        control: impl FnOnce(&mut Ui) -> R2,
88    ) -> (R1, R2) {
89        let r1 = self
90            .ui
91            .with_layout(Layout::right_to_left(Align::Center), label)
92            .inner;
93        let r2 = self
94            .ui
95            .with_layout(Layout::left_to_right(Align::Center), control)
96            .inner;
97        self.ui.end_row();
98        (r1, r2)
99    }
100
101    /// Convenience helper adding a text label and control row.
102    pub fn row_label<R>(
103        &mut self,
104        label_text: impl Into<WidgetText>,
105        control: impl FnOnce(&mut Ui) -> R,
106    ) -> (Response, R) {
107        let text = label_text.into();
108        self.row(|ui| ui.label(text), control)
109    }
110}