Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/usage/context_menu.rs
6592 views
1
//! This example illustrates how to create a context menu that changes the clear color
2
3
use bevy::{
4
color::palettes::basic,
5
ecs::{relationship::RelatedSpawner, spawn::SpawnWith},
6
prelude::*,
7
};
8
use std::fmt::Debug;
9
10
/// event opening a new context menu at position `pos`
11
#[derive(Event)]
12
struct OpenContextMenu {
13
pos: Vec2,
14
}
15
16
/// event will be sent to close currently open context menus
17
#[derive(Event)]
18
struct CloseContextMenus;
19
20
/// marker component identifying root of a context menu
21
#[derive(Component)]
22
struct ContextMenu;
23
24
/// context menu item data storing what background color `Srgba` it activates
25
#[derive(Component)]
26
struct ContextMenuItem(Srgba);
27
28
fn main() {
29
App::new()
30
.add_plugins(DefaultPlugins)
31
.add_systems(Startup, setup)
32
.add_observer(on_trigger_menu)
33
.add_observer(on_trigger_close_menus)
34
.add_observer(text_color_on_hover::<Out>(basic::WHITE.into()))
35
.add_observer(text_color_on_hover::<Over>(basic::RED.into()))
36
.run();
37
}
38
39
/// helper function to reduce code duplication when generating almost identical observers for the hover text color change effect
40
fn text_color_on_hover<T: Debug + Clone + Reflect>(
41
color: Color,
42
) -> impl FnMut(On<Pointer<T>>, Query<&mut TextColor>, Query<&Children>) {
43
move |mut event: On<Pointer<T>>,
44
mut text_color: Query<&mut TextColor>,
45
children: Query<&Children>| {
46
let Ok(children) = children.get(event.original_entity()) else {
47
return;
48
};
49
event.propagate(false);
50
51
// find the text among children and change its color
52
for child in children.iter() {
53
if let Ok(mut col) = text_color.get_mut(child) {
54
col.0 = color;
55
}
56
}
57
}
58
}
59
60
fn setup(mut commands: Commands) {
61
commands.spawn(Camera2d);
62
63
commands.spawn(background_and_button()).observe(
64
// any click bubbling up here should lead to closing any open menu
65
|_: On<Pointer<Press>>, mut commands: Commands| {
66
commands.trigger(CloseContextMenus);
67
},
68
);
69
}
70
71
fn on_trigger_close_menus(
72
_event: On<CloseContextMenus>,
73
mut commands: Commands,
74
menus: Query<Entity, With<ContextMenu>>,
75
) {
76
for e in menus.iter() {
77
commands.entity(e).despawn();
78
}
79
}
80
81
fn on_trigger_menu(event: On<OpenContextMenu>, mut commands: Commands) {
82
commands.trigger(CloseContextMenus);
83
84
let pos = event.pos;
85
86
debug!("open context menu at: {pos}");
87
88
commands
89
.spawn((
90
Name::new("context menu"),
91
ContextMenu,
92
Node {
93
position_type: PositionType::Absolute,
94
left: px(pos.x),
95
top: px(pos.y),
96
flex_direction: FlexDirection::Column,
97
..default()
98
},
99
BorderColor::all(Color::BLACK),
100
BorderRadius::all(px(4)),
101
BackgroundColor(Color::linear_rgb(0.1, 0.1, 0.1)),
102
children![
103
context_item("fuchsia", basic::FUCHSIA),
104
context_item("gray", basic::GRAY),
105
context_item("maroon", basic::MAROON),
106
context_item("purple", basic::PURPLE),
107
context_item("teal", basic::TEAL),
108
],
109
))
110
.observe(
111
|event: On<Pointer<Press>>,
112
menu_items: Query<&ContextMenuItem>,
113
mut clear_col: ResMut<ClearColor>,
114
mut commands: Commands| {
115
let target = event.original_entity();
116
117
if let Ok(item) = menu_items.get(target) {
118
clear_col.0 = item.0.into();
119
commands.trigger(CloseContextMenus);
120
}
121
},
122
);
123
}
124
125
fn context_item(text: &str, col: Srgba) -> impl Bundle {
126
(
127
Name::new(format!("item-{text}")),
128
ContextMenuItem(col),
129
Button,
130
Node {
131
padding: UiRect::all(px(5)),
132
..default()
133
},
134
children![(
135
Pickable::IGNORE,
136
Text::new(text),
137
TextFont {
138
font_size: 24.0,
139
..default()
140
},
141
TextColor(Color::WHITE),
142
)],
143
)
144
}
145
146
fn background_and_button() -> impl Bundle {
147
(
148
Name::new("background"),
149
Node {
150
width: percent(100),
151
height: percent(100),
152
align_items: AlignItems::Center,
153
justify_content: JustifyContent::Center,
154
..default()
155
},
156
ZIndex(-10),
157
Children::spawn(SpawnWith(|parent: &mut RelatedSpawner<ChildOf>| {
158
parent
159
.spawn((
160
Name::new("button"),
161
Button,
162
Node {
163
width: px(250),
164
height: px(65),
165
border: UiRect::all(px(5)),
166
justify_content: JustifyContent::Center,
167
align_items: AlignItems::Center,
168
..default()
169
},
170
BorderColor::all(Color::BLACK),
171
BorderRadius::MAX,
172
BackgroundColor(Color::BLACK),
173
children![(
174
Pickable::IGNORE,
175
Text::new("Context Menu"),
176
TextFont {
177
font_size: 28.0,
178
..default()
179
},
180
TextColor(Color::WHITE),
181
TextShadow::default(),
182
)],
183
))
184
.observe(|mut event: On<Pointer<Press>>, mut commands: Commands| {
185
// by default this event would bubble up further leading to the `CloseContextMenus`
186
// event being triggered and undoing the opening of one here right away.
187
event.propagate(false);
188
189
debug!("click: {}", event.pointer_location.position);
190
191
commands.trigger(OpenContextMenu {
192
pos: event.pointer_location.position,
193
});
194
});
195
})),
196
)
197
}
198
199