Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ui/widgets/vertical_slider.rs
9341 views
1
//! Simple example showing vertical and horizontal slider widgets with snap behavior and value labels
2
3
use bevy::{
4
input_focus::{
5
tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
6
InputDispatchPlugin,
7
},
8
picking::hover::Hovered,
9
prelude::*,
10
ui_widgets::{
11
observe, slider_self_update, CoreSliderDragState, Slider, SliderRange, SliderThumb,
12
SliderValue, TrackClick, UiWidgetsPlugins,
13
},
14
};
15
16
const SLIDER_TRACK: Color = Color::srgb(0.05, 0.05, 0.05);
17
const SLIDER_THUMB: Color = Color::srgb(0.35, 0.75, 0.35);
18
19
fn main() {
20
App::new()
21
.add_plugins((
22
DefaultPlugins,
23
UiWidgetsPlugins,
24
InputDispatchPlugin,
25
TabNavigationPlugin,
26
))
27
.add_systems(Startup, setup)
28
.add_systems(Update, (update_slider_visuals, update_value_labels))
29
.run();
30
}
31
32
#[derive(Component)]
33
struct ValueLabel(Entity);
34
35
#[derive(Component)]
36
struct DemoSlider;
37
38
#[derive(Component)]
39
struct DemoSliderThumb;
40
41
#[derive(Component)]
42
struct VerticalSlider;
43
44
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
45
commands.spawn(Camera2d);
46
47
commands
48
.spawn((
49
Node {
50
width: percent(100),
51
height: percent(100),
52
align_items: AlignItems::Center,
53
justify_content: JustifyContent::Center,
54
display: Display::Flex,
55
flex_direction: FlexDirection::Row,
56
column_gap: px(50),
57
..default()
58
},
59
TabGroup::default(),
60
))
61
.with_children(|parent| {
62
// Vertical slider
63
parent
64
.spawn(Node {
65
display: Display::Flex,
66
flex_direction: FlexDirection::Column,
67
align_items: AlignItems::Center,
68
row_gap: px(10),
69
..default()
70
})
71
.with_children(|parent| {
72
parent.spawn((
73
Text::new("Vertical"),
74
TextFont {
75
font: assets.load("fonts/FiraSans-Bold.ttf").into(),
76
font_size: FontSize::Px(20.0),
77
..default()
78
},
79
TextColor(Color::srgb(0.9, 0.9, 0.9)),
80
));
81
82
let label_id = parent
83
.spawn((
84
Text::new("50"),
85
TextFont {
86
font: assets.load("fonts/FiraSans-Bold.ttf").into(),
87
font_size: FontSize::Px(24.0),
88
..default()
89
},
90
TextColor(Color::srgb(0.9, 0.9, 0.9)),
91
))
92
.id();
93
94
parent.spawn((
95
vertical_slider(),
96
ValueLabel(label_id),
97
observe(slider_self_update),
98
));
99
});
100
101
// Horizontal slider
102
parent
103
.spawn(Node {
104
display: Display::Flex,
105
flex_direction: FlexDirection::Column,
106
align_items: AlignItems::Center,
107
row_gap: px(10),
108
..default()
109
})
110
.with_children(|parent| {
111
parent.spawn((
112
Text::new("Horizontal"),
113
TextFont {
114
font: assets.load("fonts/FiraSans-Bold.ttf").into(),
115
font_size: FontSize::Px(20.0),
116
..default()
117
},
118
TextColor(Color::srgb(0.9, 0.9, 0.9)),
119
));
120
121
let label_id = parent
122
.spawn((
123
Text::new("50"),
124
TextFont {
125
font: assets.load("fonts/FiraSans-Bold.ttf").into(),
126
font_size: FontSize::Px(24.0),
127
..default()
128
},
129
TextColor(Color::srgb(0.9, 0.9, 0.9)),
130
))
131
.id();
132
133
parent.spawn((
134
horizontal_slider(),
135
ValueLabel(label_id),
136
observe(slider_self_update),
137
));
138
});
139
});
140
}
141
142
fn vertical_slider() -> impl Bundle {
143
(
144
Node {
145
display: Display::Flex,
146
flex_direction: FlexDirection::Row,
147
justify_content: JustifyContent::Center,
148
align_items: AlignItems::Stretch,
149
column_gap: px(4),
150
width: px(12),
151
height: px(200),
152
..default()
153
},
154
DemoSlider,
155
VerticalSlider,
156
Hovered::default(),
157
Slider {
158
track_click: TrackClick::Snap,
159
},
160
SliderValue(50.0),
161
SliderRange::new(0.0, 100.0),
162
TabIndex(0),
163
Children::spawn((
164
Spawn((
165
Node {
166
width: px(6),
167
border_radius: BorderRadius::all(px(3)),
168
..default()
169
},
170
BackgroundColor(SLIDER_TRACK),
171
)),
172
Spawn((
173
Node {
174
display: Display::Flex,
175
position_type: PositionType::Absolute,
176
top: px(12),
177
bottom: px(0),
178
left: px(0),
179
right: px(0),
180
..default()
181
},
182
children![(
183
DemoSliderThumb,
184
SliderThumb,
185
Node {
186
display: Display::Flex,
187
width: px(12),
188
height: px(12),
189
position_type: PositionType::Absolute,
190
bottom: percent(0),
191
border_radius: BorderRadius::MAX,
192
..default()
193
},
194
BackgroundColor(SLIDER_THUMB),
195
)],
196
)),
197
)),
198
)
199
}
200
201
fn horizontal_slider() -> impl Bundle {
202
(
203
Node {
204
display: Display::Flex,
205
flex_direction: FlexDirection::Column,
206
justify_content: JustifyContent::Center,
207
align_items: AlignItems::Stretch,
208
column_gap: px(4),
209
height: px(12),
210
width: px(200),
211
..default()
212
},
213
DemoSlider,
214
Hovered::default(),
215
Slider {
216
track_click: TrackClick::Snap,
217
},
218
SliderValue(50.0),
219
SliderRange::new(0.0, 100.0),
220
TabIndex(0),
221
Children::spawn((
222
Spawn((
223
Node {
224
height: px(6),
225
border_radius: BorderRadius::all(px(3)),
226
..default()
227
},
228
BackgroundColor(SLIDER_TRACK),
229
)),
230
Spawn((
231
Node {
232
display: Display::Flex,
233
position_type: PositionType::Absolute,
234
left: px(0),
235
right: px(12),
236
top: px(0),
237
bottom: px(0),
238
..default()
239
},
240
children![(
241
DemoSliderThumb,
242
SliderThumb,
243
Node {
244
display: Display::Flex,
245
width: px(12),
246
height: px(12),
247
position_type: PositionType::Absolute,
248
left: percent(0),
249
border_radius: BorderRadius::MAX,
250
..default()
251
},
252
BackgroundColor(SLIDER_THUMB),
253
)],
254
)),
255
)),
256
)
257
}
258
259
fn update_slider_visuals(
260
sliders: Query<
261
(
262
Entity,
263
&SliderValue,
264
&SliderRange,
265
&Hovered,
266
&CoreSliderDragState,
267
Has<VerticalSlider>,
268
),
269
(
270
Or<(
271
Changed<SliderValue>,
272
Changed<Hovered>,
273
Changed<CoreSliderDragState>,
274
)>,
275
With<DemoSlider>,
276
),
277
>,
278
children: Query<&Children>,
279
mut thumbs: Query<(&mut Node, &mut BackgroundColor, Has<DemoSliderThumb>), Without<DemoSlider>>,
280
) {
281
for (slider_ent, value, range, hovered, drag_state, is_vertical) in sliders.iter() {
282
for child in children.iter_descendants(slider_ent) {
283
if let Ok((mut thumb_node, mut thumb_bg, is_thumb)) = thumbs.get_mut(child)
284
&& is_thumb
285
{
286
let position = range.thumb_position(value.0) * 100.0;
287
if is_vertical {
288
thumb_node.bottom = percent(position);
289
} else {
290
thumb_node.left = percent(position);
291
}
292
293
let is_active = hovered.0 | drag_state.dragging;
294
thumb_bg.0 = if is_active {
295
SLIDER_THUMB.lighter(0.3)
296
} else {
297
SLIDER_THUMB
298
};
299
}
300
}
301
}
302
}
303
304
fn update_value_labels(
305
sliders: Query<(&SliderValue, &ValueLabel), (Changed<SliderValue>, With<DemoSlider>)>,
306
mut texts: Query<&mut Text>,
307
) {
308
for (value, label) in sliders.iter() {
309
if let Ok(mut text) = texts.get_mut(label.0) {
310
**text = format!("{:.0}", value.0);
311
}
312
}
313
}
314
315