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
use crate::{widgets::*, *};
use epaint::Stroke;
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub(crate) struct BarState {
open_menu: Option<Id>,
}
impl BarState {
fn load(ctx: &Context, bar_id: &Id) -> Self {
*ctx.memory().id_data_temp.get_or_default(*bar_id)
}
fn save(self, ctx: &Context, bar_id: Id) {
ctx.memory().id_data_temp.insert(bar_id, self);
}
}
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
ui.horizontal(|ui| {
let mut style = (**ui.style()).clone();
style.spacing.button_padding = vec2(2.0, 0.0);
style.visuals.widgets.active.bg_stroke = Stroke::none();
style.visuals.widgets.hovered.bg_stroke = Stroke::none();
style.visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT;
style.visuals.widgets.inactive.bg_stroke = Stroke::none();
ui.set_style(style);
let height = ui.spacing().interact_size.y;
ui.set_min_size(vec2(ui.available_width(), height));
add_contents(ui)
})
}
pub fn menu(ui: &mut Ui, title: impl ToString, add_contents: impl FnOnce(&mut Ui)) {
menu_impl(ui, title, Box::new(add_contents))
}
#[allow(clippy::needless_pass_by_value)]
fn menu_impl<'c>(ui: &mut Ui, title: impl ToString, add_contents: Box<dyn FnOnce(&mut Ui) + 'c>) {
let title = title.to_string();
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(ui.visuals().widgets.open.bg_fill);
button = button.stroke(ui.visuals().widgets.open.bg_stroke);
}
let button_response = ui.add(button);
if button_response.clicked() {
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.ctx().memory().everything_is_visible() {
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_stroke = Stroke::none();
style.visuals.widgets.hovered.bg_stroke = Stroke::none();
style.visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT;
style.visuals.widgets.inactive.bg_stroke = Stroke::none();
ui.set_style(style);
ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents);
});
});
if ui.input().key_pressed(Key::Escape) || button_response.clicked_elsewhere() {
bar_state.open_menu = None;
}
}
bar_state.save(ui.ctx(), bar_id);
}