egui_desktop/menu/
menu_bar.rs

1use egui::{Align2, Color32, CursorIcon, FontId, Sense, TextStyle, Ui, Vec2};
2
3/// A minimal horizontal menu bar composed of clickable items.
4///
5/// This is a lightweight helper primarily used by the title bar menu system.
6/// Each `MenuItem` can optionally have an action callback that is invoked on click.
7pub struct MenuBar {
8    items: Vec<MenuItem>,
9}
10
11/// A single item displayed in the `MenuBar`.
12pub struct MenuItem {
13    /// Visible text in the bar.
14    pub label: String,
15    /// Optional callback executed when the item is clicked.
16    pub action: Option<Box<dyn Fn() + Send + Sync>>,
17}
18
19impl MenuBar {
20    /// Create an empty `MenuBar`.
21    pub fn new() -> Self {
22        Self { items: Vec::new() }
23    }
24
25    /// Append a new clickable item to the menu bar.
26    ///
27    /// Returns `self` for fluent chaining.
28    ///
29    /// # Arguments
30    /// - `label`: Text to display
31    /// - `action`: Optional callback invoked on click
32    pub fn add_item(mut self, label: &str, action: Option<Box<dyn Fn() + Send + Sync>>) -> Self {
33        self.items.push(MenuItem {
34            label: label.to_string(),
35            action,
36        });
37        self
38    }
39
40    /// Render the menu bar into the given `egui::Ui`.
41    ///
42    /// Each item is laid out horizontally and becomes highlighted on hover.
43    /// When clicked, the item's `action` callback (if any) is executed.
44    pub fn render(&self, ui: &mut Ui) {
45        let item_height = 28.0; // Match title bar height
46
47        for item in &self.items {
48            let item_width = ui.fonts(|f| {
49                f.layout_no_wrap(
50                    item.label.clone(),
51                    FontId::proportional(14.0), // Standard menu font size
52                    Color32::WHITE,             // Will be overridden by theme
53                )
54                .size()
55                .x
56            }) + 16.0;
57            let (rect, response) =
58                ui.allocate_exact_size(Vec2::new(item_width, item_height), Sense::click());
59
60            if response.hovered() {
61                ui.painter()
62                    .rect_filled(rect, 2.0, Color32::from_rgb(50, 50, 50));
63                ui.ctx().set_cursor_icon(CursorIcon::PointingHand);
64            }
65
66            ui.painter().text(
67                rect.center(),
68                Align2::CENTER_CENTER,
69                &item.label,
70                TextStyle::Body.resolve(ui.style()),
71                Color32::from_rgb(200, 200, 200),
72            );
73
74            if response.clicked() {
75                if let Some(action) = &item.action {
76                    action();
77                }
78            }
79        }
80    }
81}
82
83impl Default for MenuBar {
84    fn default() -> Self {
85        Self::new()
86    }
87}