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}