Skip to main content

egui_charts/ui_kit/buttons/
split_button.rs

1//! Split Button - icon + dropdown arrow
2//!
3//! Used for category buttons that expand to show submenus.
4//! Layout: the icon centred in the same square cell as an [`super::IconButton`], with a small
5//! downward chevron in its bottom-right corner.
6
7use crate::theme::ThemeExt;
8use egui::emath::GuiRounding;
9
10use super::{ButtonSize, ButtonVariant, finish};
11use crate::icons::{Icon, icons};
12use crate::tokens::DESIGN_TOKENS;
13use egui::{Rect, Response, Sense, Ui, Vec2, vec2};
14
15/// Split button with icon and dropdown arrow
16///
17/// Used for tool categories that expand to show submenus.
18///
19/// # Example
20///
21/// ```
22/// use egui_charts::icons::icons;
23/// use egui_charts::ui_kit::buttons::SplitButton;
24///
25/// # egui::__run_test_ui(|ui| {
26/// # let is_submenu_open = false;
27/// let response = SplitButton::new(&icons::TREND_LINE)
28///     .tooltip("Trend Lines")
29///     .expanded(is_submenu_open)
30///     .show(ui);
31///
32/// if response.clicked() {
33///     // toggle the submenu
34/// }
35/// # });
36/// ```
37pub struct SplitButton {
38    icon: &'static Icon,
39    size: ButtonSize,
40    tooltip: Option<String>,
41    expanded: bool,
42    selected: Option<bool>,
43}
44
45impl SplitButton {
46    /// Create a new split button
47    pub fn new(icon: &'static Icon) -> Self {
48        Self {
49            icon,
50            size: ButtonSize::MD,
51            tooltip: None,
52            expanded: false,
53            selected: None,
54        }
55    }
56
57    /// Set the button size
58    #[must_use]
59    pub fn size(mut self, size: ButtonSize) -> Self {
60        self.size = size;
61        self
62    }
63
64    /// Set the tooltip text
65    #[must_use]
66    pub fn tooltip(mut self, text: impl Into<String>) -> Self {
67        self.tooltip = Some(text.into());
68        self
69    }
70
71    /// Set whether the dropdown is expanded
72    #[must_use]
73    pub fn expanded(mut self, expanded: bool) -> Self {
74        self.expanded = expanded;
75        self
76    }
77
78    /// Set whether a tool in this category is selected
79    #[must_use]
80    pub fn selected(mut self, selected: bool) -> Self {
81        self.selected = Some(selected);
82        self
83    }
84
85    /// Show the button and return the response
86    pub fn show(self, ui: &mut Ui) -> Response {
87        let button_size = Vec2::splat(self.size.pixels());
88        let (rect, response) = ui.allocate_exact_size(button_size, Sense::click());
89        crate::styles::focus::draw_focus_ring(ui, &response, ui.active_theme().radius.sm);
90
91        let show_active = self.expanded || self.selected == Some(true);
92        if ui.is_rect_visible(rect) {
93            let hovered = response.hovered();
94            let pressed = response.is_pointer_button_down_on();
95            let variant = if show_active {
96                ButtonVariant::Primary
97            } else {
98                ButtonVariant::Ghost
99            };
100            ui.painter().rect_filled(
101                rect,
102                ui.active_theme().radius.sm,
103                variant.bg_color(ui, hovered, pressed),
104            );
105
106            let color = if show_active {
107                variant.fg_color(ui)
108            } else {
109                ui.style().interact(&response).fg_stroke.color
110            };
111            let icon_size = Vec2::splat(self.size.icon_pixels(None));
112            self.icon
113                .as_image_tinted(icon_size, color)
114                .paint_at(ui, Rect::from_center_size(rect.center(), icon_size));
115
116            let size = Vec2::splat(DESIGN_TOKENS.sizing.icon_sm);
117            let chevron = Rect::from_min_size(rect.right_bottom() - size + vec2(0.0, 4.0), size)
118                .round_to_pixels(ui.pixels_per_point());
119            icons::CHEVRON_DOWN
120                .as_image_tinted(size, color)
121                .paint_at(ui, chevron);
122        }
123
124        ui.ctx()
125            .accesskit_node_builder(response.id, |node| node.set_expanded(self.expanded));
126        let tooltip = self.tooltip.as_deref();
127        finish(
128            response,
129            self.selected,
130            tooltip.unwrap_or_default(),
131            tooltip,
132        )
133    }
134}