Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ui/widgets/feathers.rs
9331 views
1
//! This example shows off the various Bevy Feathers widgets.
2
3
use bevy::{
4
color::palettes,
5
feathers::{
6
controls::{
7
button, checkbox, color_plane, color_slider, color_swatch, radio, slider,
8
toggle_switch, ButtonProps, ButtonVariant, ColorChannel, ColorPlane, ColorPlaneValue,
9
ColorSlider, ColorSliderProps, ColorSwatch, ColorSwatchValue, SliderBaseColor,
10
SliderProps,
11
},
12
cursor::{EntityCursor, OverrideCursor},
13
dark_theme::create_dark_theme,
14
rounded_corners::RoundedCorners,
15
theme::{ThemeBackgroundColor, ThemedText, UiTheme},
16
tokens, FeathersPlugins,
17
},
18
input_focus::tab_navigation::TabGroup,
19
prelude::*,
20
ui::{Checked, InteractionDisabled},
21
ui_widgets::{
22
checkbox_self_update, observe, slider_self_update, Activate, RadioButton, RadioGroup,
23
SliderPrecision, SliderStep, SliderValue, ValueChange,
24
},
25
window::SystemCursorIcon,
26
};
27
28
/// A struct to hold the state of various widgets shown in the demo.
29
#[derive(Resource)]
30
struct DemoWidgetStates {
31
rgb_color: Srgba,
32
hsl_color: Hsla,
33
}
34
35
#[derive(Component, Clone, Copy, PartialEq)]
36
enum SwatchType {
37
Rgb,
38
Hsl,
39
}
40
41
#[derive(Component, Clone, Copy)]
42
struct DemoDisabledButton;
43
44
fn main() {
45
App::new()
46
.add_plugins((DefaultPlugins, FeathersPlugins))
47
.insert_resource(UiTheme(create_dark_theme()))
48
.insert_resource(DemoWidgetStates {
49
rgb_color: palettes::tailwind::EMERALD_800.with_alpha(0.7),
50
hsl_color: palettes::tailwind::AMBER_800.into(),
51
})
52
.add_systems(Startup, setup)
53
.add_systems(Update, update_colors)
54
.run();
55
}
56
57
fn setup(mut commands: Commands) {
58
// ui camera
59
commands.spawn(Camera2d);
60
commands.spawn(demo_root());
61
}
62
63
fn demo_root() -> impl Bundle {
64
(
65
Node {
66
width: percent(100),
67
height: percent(100),
68
align_items: AlignItems::Start,
69
justify_content: JustifyContent::Start,
70
display: Display::Flex,
71
flex_direction: FlexDirection::Column,
72
row_gap: px(10),
73
..default()
74
},
75
TabGroup::default(),
76
ThemeBackgroundColor(tokens::WINDOW_BG),
77
children![(
78
Node {
79
display: Display::Flex,
80
flex_direction: FlexDirection::Column,
81
align_items: AlignItems::Stretch,
82
justify_content: JustifyContent::Start,
83
padding: UiRect::all(px(8)),
84
row_gap: px(8),
85
width: percent(30),
86
min_width: px(200),
87
..default()
88
},
89
children![
90
(
91
Node {
92
display: Display::Flex,
93
flex_direction: FlexDirection::Row,
94
align_items: AlignItems::Center,
95
justify_content: JustifyContent::Start,
96
column_gap: px(8),
97
..default()
98
},
99
children![
100
(
101
button(
102
ButtonProps::default(),
103
(),
104
Spawn((Text::new("Normal"), ThemedText))
105
),
106
observe(|_activate: On<Activate>| {
107
info!("Normal button clicked!");
108
})
109
),
110
(
111
button(
112
ButtonProps::default(),
113
(InteractionDisabled, DemoDisabledButton),
114
Spawn((Text::new("Disabled"), ThemedText))
115
),
116
observe(|_activate: On<Activate>| {
117
info!("Disabled button clicked!");
118
})
119
),
120
(
121
button(
122
ButtonProps {
123
variant: ButtonVariant::Primary,
124
..default()
125
},
126
(),
127
Spawn((Text::new("Primary"), ThemedText))
128
),
129
observe(|_activate: On<Activate>| {
130
info!("Disabled button clicked!");
131
})
132
),
133
]
134
),
135
(
136
Node {
137
display: Display::Flex,
138
flex_direction: FlexDirection::Row,
139
align_items: AlignItems::Center,
140
justify_content: JustifyContent::Start,
141
column_gap: px(1),
142
..default()
143
},
144
children![
145
(
146
button(
147
ButtonProps {
148
corners: RoundedCorners::Left,
149
..default()
150
},
151
(),
152
Spawn((Text::new("Left"), ThemedText))
153
),
154
observe(|_activate: On<Activate>| {
155
info!("Left button clicked!");
156
})
157
),
158
(
159
button(
160
ButtonProps {
161
corners: RoundedCorners::None,
162
..default()
163
},
164
(),
165
Spawn((Text::new("Center"), ThemedText))
166
),
167
observe(|_activate: On<Activate>| {
168
info!("Center button clicked!");
169
})
170
),
171
(
172
button(
173
ButtonProps {
174
variant: ButtonVariant::Primary,
175
corners: RoundedCorners::Right,
176
},
177
(),
178
Spawn((Text::new("Right"), ThemedText))
179
),
180
observe(|_activate: On<Activate>| {
181
info!("Right button clicked!");
182
})
183
),
184
]
185
),
186
(
187
button(
188
ButtonProps::default(),
189
(),
190
Spawn((Text::new("Toggle override"), ThemedText))
191
),
192
observe(|_activate: On<Activate>, mut ovr: ResMut<OverrideCursor>| {
193
ovr.0 = if ovr.0.is_some() {
194
None
195
} else {
196
Some(EntityCursor::System(SystemCursorIcon::Wait))
197
};
198
info!("Override cursor button clicked!");
199
})
200
),
201
(
202
checkbox(Checked, Spawn((Text::new("Checkbox"), ThemedText))),
203
observe(
204
|change: On<ValueChange<bool>>,
205
query: Query<Entity, With<DemoDisabledButton>>,
206
mut commands: Commands| {
207
info!("Checkbox clicked!");
208
let mut button = commands.entity(query.single().unwrap());
209
if change.value {
210
button.insert(InteractionDisabled);
211
} else {
212
button.remove::<InteractionDisabled>();
213
}
214
let mut checkbox = commands.entity(change.source);
215
if change.value {
216
checkbox.insert(Checked);
217
} else {
218
checkbox.remove::<Checked>();
219
}
220
}
221
)
222
),
223
(
224
checkbox(
225
InteractionDisabled,
226
Spawn((Text::new("Disabled"), ThemedText))
227
),
228
observe(|_change: On<ValueChange<bool>>| {
229
warn!("Disabled checkbox clicked!");
230
})
231
),
232
(
233
checkbox(
234
(InteractionDisabled, Checked),
235
Spawn((Text::new("Disabled+Checked"), ThemedText))
236
),
237
observe(|_change: On<ValueChange<bool>>| {
238
warn!("Disabled checkbox clicked!");
239
})
240
),
241
(
242
Node {
243
display: Display::Flex,
244
flex_direction: FlexDirection::Column,
245
row_gap: px(4),
246
..default()
247
},
248
RadioGroup,
249
observe(
250
|value_change: On<ValueChange<Entity>>,
251
q_radio: Query<Entity, With<RadioButton>>,
252
mut commands: Commands| {
253
for radio in q_radio.iter() {
254
if radio == value_change.value {
255
commands.entity(radio).insert(Checked);
256
} else {
257
commands.entity(radio).remove::<Checked>();
258
}
259
}
260
}
261
),
262
children![
263
radio(Checked, Spawn((Text::new("One"), ThemedText))),
264
radio((), Spawn((Text::new("Two"), ThemedText))),
265
radio((), Spawn((Text::new("Three"), ThemedText))),
266
radio(
267
InteractionDisabled,
268
Spawn((Text::new("Disabled"), ThemedText))
269
),
270
]
271
),
272
(
273
Node {
274
display: Display::Flex,
275
flex_direction: FlexDirection::Row,
276
align_items: AlignItems::Center,
277
justify_content: JustifyContent::Start,
278
column_gap: px(8),
279
..default()
280
},
281
children![
282
(toggle_switch((),), observe(checkbox_self_update)),
283
(
284
toggle_switch(InteractionDisabled,),
285
observe(checkbox_self_update)
286
),
287
(
288
toggle_switch((InteractionDisabled, Checked),),
289
observe(checkbox_self_update)
290
),
291
]
292
),
293
(
294
slider(
295
SliderProps {
296
max: 100.0,
297
value: 20.0,
298
..default()
299
},
300
(SliderStep(10.), SliderPrecision(2)),
301
),
302
observe(slider_self_update)
303
),
304
(
305
Node {
306
display: Display::Flex,
307
flex_direction: FlexDirection::Row,
308
justify_content: JustifyContent::SpaceBetween,
309
..default()
310
},
311
children![Text("Srgba".to_owned()), color_swatch(SwatchType::Rgb),]
312
),
313
(
314
color_plane(ColorPlane::RedBlue, ()),
315
observe(
316
|change: On<ValueChange<Vec2>>, mut color: ResMut<DemoWidgetStates>| {
317
color.rgb_color.red = change.value.x;
318
color.rgb_color.blue = change.value.y;
319
}
320
)
321
),
322
(
323
color_slider(
324
ColorSliderProps {
325
value: 0.5,
326
channel: ColorChannel::Red
327
},
328
()
329
),
330
observe(
331
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
332
color.rgb_color.red = change.value;
333
}
334
)
335
),
336
(
337
color_slider(
338
ColorSliderProps {
339
value: 0.5,
340
channel: ColorChannel::Green
341
},
342
()
343
),
344
observe(
345
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
346
color.rgb_color.green = change.value;
347
},
348
)
349
),
350
(
351
color_slider(
352
ColorSliderProps {
353
value: 0.5,
354
channel: ColorChannel::Blue
355
},
356
()
357
),
358
observe(
359
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
360
color.rgb_color.blue = change.value;
361
},
362
)
363
),
364
(
365
color_slider(
366
ColorSliderProps {
367
value: 0.5,
368
channel: ColorChannel::Alpha
369
},
370
()
371
),
372
observe(
373
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
374
color.rgb_color.alpha = change.value;
375
},
376
)
377
),
378
(
379
Node {
380
display: Display::Flex,
381
flex_direction: FlexDirection::Row,
382
justify_content: JustifyContent::SpaceBetween,
383
..default()
384
},
385
children![Text("Hsl".to_owned()), color_swatch(SwatchType::Hsl),]
386
),
387
(
388
color_slider(
389
ColorSliderProps {
390
value: 0.5,
391
channel: ColorChannel::HslHue
392
},
393
()
394
),
395
observe(
396
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
397
color.hsl_color.hue = change.value;
398
},
399
)
400
),
401
(
402
color_slider(
403
ColorSliderProps {
404
value: 0.5,
405
channel: ColorChannel::HslSaturation
406
},
407
()
408
),
409
observe(
410
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
411
color.hsl_color.saturation = change.value;
412
},
413
)
414
),
415
(
416
color_slider(
417
ColorSliderProps {
418
value: 0.5,
419
channel: ColorChannel::HslLightness
420
},
421
()
422
),
423
observe(
424
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
425
color.hsl_color.lightness = change.value;
426
},
427
)
428
)
429
]
430
),],
431
)
432
}
433
434
fn update_colors(
435
colors: Res<DemoWidgetStates>,
436
mut sliders: Query<(Entity, &ColorSlider, &mut SliderBaseColor)>,
437
mut swatches: Query<(&mut ColorSwatchValue, &SwatchType), With<ColorSwatch>>,
438
mut color_planes: Query<&mut ColorPlaneValue, With<ColorPlane>>,
439
mut commands: Commands,
440
) {
441
if colors.is_changed() {
442
for (slider_ent, slider, mut base) in sliders.iter_mut() {
443
match slider.channel {
444
ColorChannel::Red => {
445
base.0 = colors.rgb_color.into();
446
commands
447
.entity(slider_ent)
448
.insert(SliderValue(colors.rgb_color.red));
449
}
450
ColorChannel::Green => {
451
base.0 = colors.rgb_color.into();
452
commands
453
.entity(slider_ent)
454
.insert(SliderValue(colors.rgb_color.green));
455
}
456
ColorChannel::Blue => {
457
base.0 = colors.rgb_color.into();
458
commands
459
.entity(slider_ent)
460
.insert(SliderValue(colors.rgb_color.blue));
461
}
462
ColorChannel::HslHue => {
463
base.0 = colors.hsl_color.into();
464
commands
465
.entity(slider_ent)
466
.insert(SliderValue(colors.hsl_color.hue));
467
}
468
ColorChannel::HslSaturation => {
469
base.0 = colors.hsl_color.into();
470
commands
471
.entity(slider_ent)
472
.insert(SliderValue(colors.hsl_color.saturation));
473
}
474
ColorChannel::HslLightness => {
475
base.0 = colors.hsl_color.into();
476
commands
477
.entity(slider_ent)
478
.insert(SliderValue(colors.hsl_color.lightness));
479
}
480
ColorChannel::Alpha => {
481
base.0 = colors.rgb_color.into();
482
commands
483
.entity(slider_ent)
484
.insert(SliderValue(colors.rgb_color.alpha));
485
}
486
}
487
}
488
489
for (mut swatch_value, swatch_type) in swatches.iter_mut() {
490
swatch_value.0 = match swatch_type {
491
SwatchType::Rgb => colors.rgb_color.into(),
492
SwatchType::Hsl => colors.hsl_color.into(),
493
};
494
}
495
496
for mut plane_value in color_planes.iter_mut() {
497
plane_value.0.x = colors.rgb_color.red;
498
plane_value.0.y = colors.rgb_color.blue;
499
plane_value.0.z = colors.rgb_color.green;
500
}
501
}
502
}
503
504