Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/helpers/widgets.rs
6592 views
1
//! Simple widgets for example UI.
2
//!
3
//! Unlike other examples, which demonstrate an application, this demonstrates a plugin library.
4
5
use bevy::prelude::*;
6
7
/// An event that's sent whenever the user changes one of the settings by
8
/// clicking a radio button.
9
#[derive(Clone, BufferedEvent, Deref, DerefMut)]
10
pub struct WidgetClickEvent<T>(T);
11
12
/// A marker component that we place on all widgets that send
13
/// [`WidgetClickEvent`]s of the given type.
14
#[derive(Clone, Component, Deref, DerefMut)]
15
pub struct WidgetClickSender<T>(T)
16
where
17
T: Clone + Send + Sync + 'static;
18
19
/// A marker component that we place on all radio `Button`s.
20
#[derive(Clone, Copy, Component)]
21
pub struct RadioButton;
22
23
/// A marker component that we place on all `Text` inside radio buttons.
24
#[derive(Clone, Copy, Component)]
25
pub struct RadioButtonText;
26
27
/// The size of the border that surrounds buttons.
28
pub const BUTTON_BORDER: UiRect = UiRect::all(Val::Px(1.0));
29
30
/// The color of the border that surrounds buttons.
31
pub const BUTTON_BORDER_COLOR: BorderColor = BorderColor {
32
left: Color::WHITE,
33
right: Color::WHITE,
34
top: Color::WHITE,
35
bottom: Color::WHITE,
36
};
37
38
/// The amount of rounding to apply to button corners.
39
pub const BUTTON_BORDER_RADIUS_SIZE: Val = Val::Px(6.0);
40
41
/// The amount of space between the edge of the button and its label.
42
pub const BUTTON_PADDING: UiRect = UiRect::axes(Val::Px(12.0), Val::Px(6.0));
43
44
/// Returns a [`Node`] appropriate for the outer main UI node.
45
///
46
/// This UI is in the bottom left corner and has flex column support
47
pub fn main_ui_node() -> Node {
48
Node {
49
flex_direction: FlexDirection::Column,
50
position_type: PositionType::Absolute,
51
row_gap: px(6),
52
left: px(10),
53
bottom: px(10),
54
..default()
55
}
56
}
57
58
/// Spawns a single radio button that allows configuration of a setting.
59
///
60
/// The type parameter specifies the value that will be packaged up and sent in
61
/// a [`WidgetClickEvent`] when the radio button is clicked.
62
pub fn option_button<T>(
63
option_value: T,
64
option_name: &str,
65
is_selected: bool,
66
is_first: bool,
67
is_last: bool,
68
) -> impl Bundle
69
where
70
T: Clone + Send + Sync + 'static,
71
{
72
let (bg_color, fg_color) = if is_selected {
73
(Color::WHITE, Color::BLACK)
74
} else {
75
(Color::BLACK, Color::WHITE)
76
};
77
78
// Add the button node.
79
(
80
Button,
81
Node {
82
border: BUTTON_BORDER.with_left(if is_first { px(1) } else { px(0) }),
83
justify_content: JustifyContent::Center,
84
align_items: AlignItems::Center,
85
padding: BUTTON_PADDING,
86
..default()
87
},
88
BUTTON_BORDER_COLOR,
89
BorderRadius::ZERO
90
.with_left(if is_first {
91
BUTTON_BORDER_RADIUS_SIZE
92
} else {
93
px(0)
94
})
95
.with_right(if is_last {
96
BUTTON_BORDER_RADIUS_SIZE
97
} else {
98
px(0)
99
}),
100
BackgroundColor(bg_color),
101
RadioButton,
102
WidgetClickSender(option_value.clone()),
103
children![(
104
ui_text(option_name, fg_color),
105
RadioButtonText,
106
WidgetClickSender(option_value),
107
)],
108
)
109
}
110
111
/// Spawns the buttons that allow configuration of a setting.
112
///
113
/// The user may change the setting to any one of the labeled `options`. The
114
/// value of the given type parameter will be packaged up and sent as a
115
/// [`WidgetClickEvent`] when one of the radio buttons is clicked.
116
pub fn option_buttons<T>(title: &str, options: &[(T, &str)]) -> impl Bundle
117
where
118
T: Clone + Send + Sync + 'static,
119
{
120
let buttons = options
121
.iter()
122
.cloned()
123
.enumerate()
124
.map(|(option_index, (option_value, option_name))| {
125
option_button(
126
option_value,
127
option_name,
128
option_index == 0,
129
option_index == 0,
130
option_index == options.len() - 1,
131
)
132
})
133
.collect::<Vec<_>>();
134
// Add the parent node for the row.
135
(
136
Node {
137
align_items: AlignItems::Center,
138
..default()
139
},
140
Children::spawn((
141
Spawn((
142
ui_text(title, Color::BLACK),
143
Node {
144
width: px(125),
145
..default()
146
},
147
)),
148
SpawnIter(buttons.into_iter()),
149
)),
150
)
151
}
152
153
/// Creates a text bundle for the UI.
154
pub fn ui_text(label: &str, color: Color) -> impl Bundle + use<> {
155
(
156
Text::new(label),
157
TextFont {
158
font_size: 18.0,
159
..default()
160
},
161
TextColor(color),
162
)
163
}
164
165
/// Checks for clicks on the radio buttons and sends `RadioButtonChangeEvent`s
166
/// as necessary.
167
pub fn handle_ui_interactions<T>(
168
mut interactions: Query<
169
(&Interaction, &WidgetClickSender<T>),
170
(With<Button>, With<RadioButton>),
171
>,
172
mut widget_click_events: EventWriter<WidgetClickEvent<T>>,
173
) where
174
T: Clone + Send + Sync + 'static,
175
{
176
for (interaction, click_event) in interactions.iter_mut() {
177
if *interaction == Interaction::Pressed {
178
widget_click_events.write(WidgetClickEvent((**click_event).clone()));
179
}
180
}
181
}
182
183
/// Updates the style of the button part of a radio button to reflect its
184
/// selected status.
185
pub fn update_ui_radio_button(background_color: &mut BackgroundColor, selected: bool) {
186
background_color.0 = if selected { Color::WHITE } else { Color::BLACK };
187
}
188
189
/// Updates the color of the label of a radio button to reflect its selected
190
/// status.
191
pub fn update_ui_radio_button_text(entity: Entity, writer: &mut TextUiWriter, selected: bool) {
192
let text_color = if selected { Color::BLACK } else { Color::WHITE };
193
194
writer.for_each_color(entity, |mut color| {
195
color.0 = text_color;
196
});
197
}
198
199