1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Menu bar functionality (very basic so far).
//!
//! Usage:
//! ```
//! fn show_menu(ui: &mut egui::Ui) {
//!     use egui::{menu, Button};
//!
//!     menu::bar(ui, |ui| {
//!         menu::menu(ui, "File", |ui| {
//!             if ui.button("Open").clicked {
//!                 // ...
//!             }
//!         });
//!     });
//! }
//! ```

use crate::{color::TRANSPARENT, paint::Stroke, widgets::*, *};

/// What is saved between frames.
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct BarState {
    open_menu: Option<Id>,
}

impl BarState {
    fn load(ctx: &Context, bar_id: &Id) -> Self {
        ctx.memory()
            .menu_bar
            .get(bar_id)
            .cloned()
            .unwrap_or_default()
    }

    fn save(self, ctx: &Context, bar_id: Id) {
        ctx.memory().menu_bar.insert(bar_id, self);
    }
}

/// The menu bar goes well in `TopPanel`,
/// but can also be placed in a `Window`.
/// In the latter case you may want to wrap it in `Frame`.
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) {
    ui.horizontal(|ui| {
        let mut style = ui.style().clone();
        style.spacing.button_padding = vec2(2.0, 0.0);
        // style.visuals.widgets.active.bg_fill = TRANSPARENT;
        style.visuals.widgets.active.bg_stroke = Stroke::none();
        // style.visuals.widgets.hovered.bg_fill = TRANSPARENT;
        style.visuals.widgets.hovered.bg_stroke = Stroke::none();
        style.visuals.widgets.inactive.bg_fill = TRANSPARENT;
        style.visuals.widgets.inactive.bg_stroke = Stroke::none();
        ui.set_style(style);

        // Take full width and fixed height:
        let height = ui.style().spacing.interact_size.y;
        ui.set_min_size(vec2(ui.available().width(), height));

        add_contents(ui)
    })
}

/// Construct a top level menu in a menu bar. This would be e.g. "File", "Edit" etc.
pub fn menu(ui: &mut Ui, title: impl Into<String>, add_contents: impl FnOnce(&mut Ui)) {
    menu_impl(ui, title, Box::new(add_contents))
}

fn menu_impl<'c>(
    ui: &mut Ui,
    title: impl Into<String>,
    add_contents: Box<dyn FnOnce(&mut Ui) + 'c>,
) {
    let title = title.into();
    let bar_id = ui.id();
    let menu_id = bar_id.with(&title);

    let mut bar_state = BarState::load(ui.ctx(), &bar_id);

    let mut button = Button::new(title);

    if bar_state.open_menu == Some(menu_id) {
        button = button.fill(Some(ui.style().visuals.widgets.active.fg_fill));
    }

    let button_response = ui.add(button);
    if button_response.clicked {
        // Toggle
        if bar_state.open_menu == Some(menu_id) {
            bar_state.open_menu = None;
        } else {
            bar_state.open_menu = Some(menu_id);
        }
    } else if button_response.hovered && bar_state.open_menu.is_some() {
        bar_state.open_menu = Some(menu_id);
    }

    if bar_state.open_menu == Some(menu_id) || ui.memory().all_menues_are_open {
        let area = Area::new(menu_id)
            .order(Order::Foreground)
            .fixed_pos(button_response.rect.left_bottom());
        let frame = Frame::menu(ui.style());

        area.show(ui.ctx(), |ui| {
            frame.show(ui, |ui| {
                let mut style = ui.style().clone();
                style.spacing.button_padding = vec2(2.0, 0.0);
                // style.visuals.widgets.active.bg_fill = TRANSPARENT;
                style.visuals.widgets.active.bg_stroke = Stroke::none();
                // style.visuals.widgets.hovered.bg_fill = TRANSPARENT;
                style.visuals.widgets.hovered.bg_stroke = Stroke::none();
                style.visuals.widgets.inactive.bg_fill = TRANSPARENT;
                style.visuals.widgets.inactive.bg_stroke = Stroke::none();
                ui.set_style(style);
                ui.with_layout(Layout::justified(Direction::Vertical), add_contents);
            })
        });

        // TODO: this prevents sub-menus in menus. We should fix that.
        if ui.input().key_pressed(Key::Escape) || ui.input().mouse.click && !button_response.clicked
        {
            bar_state.open_menu = None;
        }
    }

    bar_state.save(ui.ctx(), bar_id);
}