Skip to main content

egui_charts/ui_kit/toolbar/
button_group.rs

1//! Button Group Component
2//!
3//! Groups toolbar buttons with optional separators between groups.
4
5use egui::{Align, Layout, Ui};
6
7use crate::theme::ThemeExt;
8
9/// A group of toolbar buttons with optional separator
10///
11/// # Example
12/// ```
13/// # use egui_charts::ui_kit::toolbar::ButtonGroup;
14/// # egui::__run_test_ui(|ui| {
15/// ButtonGroup::new()
16///     .separator_after()
17///     .show(ui, |ui| {
18///         ui.button("Action 1");
19///         ui.button("Action 2");
20///     });
21/// # });
22/// ```
23pub struct ButtonGroup {
24    separator_after: bool,
25    separator_before: bool,
26}
27
28impl Default for ButtonGroup {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl ButtonGroup {
35    /// Create a new button group
36    pub fn new() -> Self {
37        Self {
38            separator_after: false,
39            separator_before: false,
40        }
41    }
42
43    /// Add a separator after this group
44    #[must_use]
45    pub fn separator_after(mut self) -> Self {
46        self.separator_after = true;
47        self
48    }
49
50    /// Add a separator before this group
51    #[must_use]
52    pub fn separator_before(mut self) -> Self {
53        self.separator_before = true;
54        self
55    }
56
57    /// Show the button group along the parent's layout direction
58    ///
59    /// The buttons sit side by side in a horizontal parent and stacked in a vertical one. Each
60    /// separator is a 1 px line across the group, with a small gap on either side. Returns the
61    /// result of the content closure.
62    pub fn show<R>(self, ui: &mut Ui, content: impl FnOnce(&mut Ui) -> R) -> R {
63        let gap = ui.active_theme().spacing.sm;
64        let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
65        let slot = 2.0 * gap + stroke.width;
66        let horizontal = ui.layout().main_dir().is_horizontal();
67        let body = |ui: &mut Ui| {
68            if self.separator_before {
69                ui.add_space(slot);
70            }
71            let result = content(ui);
72            let group = ui.min_rect();
73            // Half a stroke in, so the line covers whole pixels.
74            let offset = gap + stroke.width / 2.0;
75            if horizontal {
76                if self.separator_before {
77                    ui.painter()
78                        .vline(group.left() + offset, group.y_range(), stroke);
79                }
80                if self.separator_after {
81                    ui.painter()
82                        .vline(group.right() + offset, group.y_range(), stroke);
83                    ui.expand_to_include_x(group.right() + slot);
84                }
85            } else {
86                if self.separator_before {
87                    ui.painter()
88                        .hline(group.x_range(), group.top() + offset, stroke);
89                }
90                if self.separator_after {
91                    ui.painter()
92                        .hline(group.x_range(), group.bottom() + offset, stroke);
93                    ui.expand_to_include_y(group.bottom() + slot);
94                }
95            }
96            result
97        };
98        if horizontal {
99            ui.horizontal(body).inner
100        } else {
101            ui.vertical(body).inner
102        }
103    }
104}
105
106/// Helper for common toolbar layout patterns
107pub struct ToolbarLayout;
108
109impl ToolbarLayout {
110    /// Create a left-right split layout
111    ///
112    /// Renders left content normally (left-to-right), then renders right content
113    /// in right-to-left layout at the end.
114    ///
115    /// # Example
116    /// ```
117    /// # use egui_charts::ui_kit::toolbar::ToolbarLayout;
118    /// # egui::__run_test_ui(|ui| {
119    /// ToolbarLayout::left_right(ui,
120    ///     |ui| {
121    ///         ui.button("Left 1");
122    ///         ui.button("Left 2");
123    ///     },
124    ///     |ui| {
125    ///         ui.button("Right 1");
126    ///     },
127    /// );
128    /// # });
129    /// ```
130    pub fn left_right<L, R>(
131        ui: &mut Ui,
132        left_content: impl FnOnce(&mut Ui) -> L,
133        right_content: impl FnOnce(&mut Ui) -> R,
134    ) -> (L, R) {
135        let left = left_content(ui);
136
137        let right = ui
138            .with_layout(Layout::right_to_left(Align::Center), |ui| right_content(ui))
139            .inner;
140
141        (left, right)
142    }
143
144    /// Create a centered layout with optional left/right sections
145    ///
146    /// Useful for toolbars where the main content is centered.
147    pub fn centered<C, L, R>(
148        ui: &mut Ui,
149        left_content: impl FnOnce(&mut Ui) -> L,
150        center_content: impl FnOnce(&mut Ui) -> C,
151        right_content: impl FnOnce(&mut Ui) -> R,
152    ) -> (L, C, R) {
153        let left = left_content(ui);
154
155        let center = ui
156            .with_layout(
157                Layout::centered_and_justified(ui.layout().main_dir()),
158                |ui| center_content(ui),
159            )
160            .inner;
161
162        let right = ui
163            .with_layout(Layout::right_to_left(Align::Center), |ui| right_content(ui))
164            .inner;
165
166        (left, center, right)
167    }
168}