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