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