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