Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_feathers/src/controls/slider.rs
9416 views
1
use core::f32::consts::PI;
2
3
use bevy_app::{Plugin, PreUpdate};
4
use bevy_color::Color;
5
use bevy_ecs::{
6
bundle::Bundle,
7
children,
8
component::Component,
9
entity::Entity,
10
hierarchy::Children,
11
lifecycle::RemovedComponents,
12
query::{Added, Changed, Has, Or, Spawned, With},
13
reflect::ReflectComponent,
14
schedule::IntoScheduleConfigs,
15
system::{Commands, Query, Res},
16
};
17
use bevy_input_focus::tab_navigation::TabIndex;
18
use bevy_picking::PickingSystems;
19
use bevy_reflect::{prelude::ReflectDefault, Reflect};
20
use bevy_text::FontSize;
21
use bevy_ui::{
22
widget::Text, AlignItems, BackgroundGradient, ColorStop, Display, FlexDirection, Gradient,
23
InteractionDisabled, InterpolationColorSpace, JustifyContent, LinearGradient, Node,
24
PositionType, UiRect, Val,
25
};
26
use bevy_ui_widgets::{Slider, SliderPrecision, SliderRange, SliderValue, TrackClick};
27
28
use crate::{
29
constants::{fonts, size},
30
cursor::EntityCursor,
31
font_styles::InheritableFont,
32
handle_or_path::HandleOrPath,
33
rounded_corners::RoundedCorners,
34
theme::{ThemeFontColor, ThemedText, UiTheme},
35
tokens,
36
};
37
38
/// Slider template properties, passed to [`slider`] function.
39
pub struct SliderProps {
40
/// Slider current value
41
pub value: f32,
42
/// Slider minimum value
43
pub min: f32,
44
/// Slider maximum value
45
pub max: f32,
46
}
47
48
impl Default for SliderProps {
49
fn default() -> Self {
50
Self {
51
value: 0.0,
52
min: 0.0,
53
max: 1.0,
54
}
55
}
56
}
57
58
#[derive(Component, Default, Clone)]
59
#[require(Slider)]
60
#[derive(Reflect)]
61
#[reflect(Component, Clone, Default)]
62
struct SliderStyle;
63
64
/// Marker for the text
65
#[derive(Component, Default, Clone, Reflect)]
66
#[reflect(Component, Clone, Default)]
67
struct SliderValueText;
68
69
/// Spawn a new slider widget.
70
///
71
/// # Arguments
72
///
73
/// * `props` - construction properties for the slider.
74
/// * `overrides` - a bundle of components that are merged in with the normal slider components.
75
///
76
/// # Emitted events
77
///
78
/// * [`bevy_ui_widgets::ValueChange<f32>`] when the slider value is changed.
79
///
80
/// These events can be disabled by adding an [`bevy_ui::InteractionDisabled`] component to the entity
81
pub fn slider<B: Bundle>(props: SliderProps, overrides: B) -> impl Bundle {
82
(
83
Node {
84
height: size::ROW_HEIGHT,
85
justify_content: JustifyContent::Center,
86
align_items: AlignItems::Center,
87
padding: UiRect::axes(Val::Px(8.0), Val::Px(0.)),
88
flex_grow: 1.0,
89
border_radius: RoundedCorners::All.to_border_radius(6.0),
90
..Default::default()
91
},
92
Slider {
93
track_click: TrackClick::Drag,
94
},
95
SliderStyle,
96
SliderValue(props.value),
97
SliderRange::new(props.min, props.max),
98
EntityCursor::System(bevy_window::SystemCursorIcon::EwResize),
99
TabIndex(0),
100
// Use a gradient to draw the moving bar
101
BackgroundGradient(vec![Gradient::Linear(LinearGradient {
102
angle: PI * 0.5,
103
stops: vec![
104
ColorStop::new(Color::NONE, Val::Percent(0.)),
105
ColorStop::new(Color::NONE, Val::Percent(50.)),
106
ColorStop::new(Color::NONE, Val::Percent(50.)),
107
ColorStop::new(Color::NONE, Val::Percent(100.)),
108
],
109
color_space: InterpolationColorSpace::Srgba,
110
})]),
111
overrides,
112
children![(
113
// Text container
114
Node {
115
display: Display::Flex,
116
position_type: PositionType::Absolute,
117
flex_direction: FlexDirection::Row,
118
align_items: AlignItems::Center,
119
justify_content: JustifyContent::Center,
120
..Default::default()
121
},
122
ThemeFontColor(tokens::SLIDER_TEXT),
123
InheritableFont {
124
font: HandleOrPath::Path(fonts::MONO.to_owned()),
125
font_size: FontSize::Px(12.0),
126
},
127
children![(Text::new("10.0"), ThemedText, SliderValueText,)],
128
)],
129
)
130
}
131
132
fn update_slider_styles(
133
mut q_sliders: Query<
134
(Entity, Has<InteractionDisabled>, &mut BackgroundGradient),
135
(With<SliderStyle>, Or<(Spawned, Added<InteractionDisabled>)>),
136
>,
137
theme: Res<UiTheme>,
138
mut commands: Commands,
139
) {
140
for (slider_ent, disabled, mut gradient) in q_sliders.iter_mut() {
141
set_slider_styles(
142
slider_ent,
143
&theme,
144
disabled,
145
gradient.as_mut(),
146
&mut commands,
147
);
148
}
149
}
150
151
fn update_slider_styles_remove(
152
mut q_sliders: Query<(Entity, Has<InteractionDisabled>, &mut BackgroundGradient)>,
153
mut removed_disabled: RemovedComponents<InteractionDisabled>,
154
theme: Res<UiTheme>,
155
mut commands: Commands,
156
) {
157
removed_disabled.read().for_each(|ent| {
158
if let Ok((slider_ent, disabled, mut gradient)) = q_sliders.get_mut(ent) {
159
set_slider_styles(
160
slider_ent,
161
&theme,
162
disabled,
163
gradient.as_mut(),
164
&mut commands,
165
);
166
}
167
});
168
}
169
170
fn set_slider_styles(
171
slider_ent: Entity,
172
theme: &Res<'_, UiTheme>,
173
disabled: bool,
174
gradient: &mut BackgroundGradient,
175
commands: &mut Commands,
176
) {
177
let bar_color = theme.color(&match disabled {
178
true => tokens::SLIDER_BAR_DISABLED,
179
false => tokens::SLIDER_BAR,
180
});
181
182
let bg_color = theme.color(&tokens::SLIDER_BG);
183
184
let cursor_shape = match disabled {
185
true => bevy_window::SystemCursorIcon::NotAllowed,
186
false => bevy_window::SystemCursorIcon::EwResize,
187
};
188
189
if let [Gradient::Linear(linear_gradient)] = &mut gradient.0[..] {
190
linear_gradient.stops[0].color = bar_color;
191
linear_gradient.stops[1].color = bar_color;
192
linear_gradient.stops[2].color = bg_color;
193
linear_gradient.stops[3].color = bg_color;
194
}
195
196
// Change cursor shape
197
commands
198
.entity(slider_ent)
199
.insert(EntityCursor::System(cursor_shape));
200
}
201
202
fn update_slider_pos(
203
mut q_sliders: Query<
204
(
205
Entity,
206
&SliderValue,
207
&SliderRange,
208
&SliderPrecision,
209
&mut BackgroundGradient,
210
),
211
(
212
With<SliderStyle>,
213
Or<(
214
Changed<SliderValue>,
215
Changed<SliderRange>,
216
Changed<Children>,
217
)>,
218
),
219
>,
220
q_children: Query<&Children>,
221
mut q_slider_text: Query<&mut Text, With<SliderValueText>>,
222
) {
223
for (slider_ent, value, range, precision, mut gradient) in q_sliders.iter_mut() {
224
if let [Gradient::Linear(linear_gradient)] = &mut gradient.0[..] {
225
let percent_value = (range.thumb_position(value.0) * 100.0).clamp(0.0, 100.0);
226
linear_gradient.stops[1].point = Val::Percent(percent_value);
227
linear_gradient.stops[2].point = Val::Percent(percent_value);
228
}
229
230
// Find slider text child entity and update its text with the formatted value
231
q_children.iter_descendants(slider_ent).for_each(|child| {
232
if let Ok(mut text) = q_slider_text.get_mut(child) {
233
let label = format!("{}", value.0);
234
let decimals_len = label
235
.split_once('.')
236
.map(|(_, decimals)| decimals.len() as i32)
237
.unwrap_or(precision.0);
238
239
// Don't format with precision if the value has more decimals than the precision
240
text.0 = if precision.0 >= 0 && decimals_len <= precision.0 {
241
format!("{:.precision$}", value.0, precision = precision.0 as usize)
242
} else {
243
label
244
};
245
}
246
});
247
}
248
}
249
250
/// Plugin which registers the systems for updating the slider styles.
251
pub struct SliderPlugin;
252
253
impl Plugin for SliderPlugin {
254
fn build(&self, app: &mut bevy_app::App) {
255
app.add_systems(
256
PreUpdate,
257
(
258
update_slider_styles,
259
update_slider_styles_remove,
260
update_slider_pos,
261
)
262
.in_set(PickingSystems::Last),
263
);
264
}
265
}
266
267