1use bevy::{
10 color::palettes::basic::*,
11 input_focus::{
12 tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
13 FocusCause, InputFocus,
14 },
15 picking::hover::Hovered,
16 prelude::*,
17 ui::{Checked, InteractionDisabled, Pressed},
18 ui_widgets::{
19 checkbox_self_update, observe,
20 popover::{Popover, PopoverAlign, PopoverPlacement, PopoverSide},
21 Activate, Button, Checkbox, MenuAction, MenuButton, MenuEvent, MenuItem, MenuPopup,
22 RadioButton, RadioGroup, Slider, SliderDragState, SliderRange, SliderThumb, SliderValue,
23 TrackClick, ValueChange,
24 },
25};
26
27fn main() {
28 App::new()
29 .add_plugins((DefaultPlugins, TabNavigationPlugin))
30 .insert_resource(DemoWidgetStates {
31 slider_value: 50.0,
32 slider_click: TrackClick::Snap,
33 })
34 .add_systems(Startup, setup)
35 .add_systems(
36 Update,
37 (
38 update_widget_values,
39 update_button_style,
40 update_button_style2,
41 update_slider_style.after(update_widget_values),
42 update_slider_style2.after(update_widget_values),
43 update_checkbox_or_radio_style.after(update_widget_values),
44 update_checkbox_or_radio_style2.after(update_widget_values),
45 update_menu_item_style,
46 update_menu_item_style2,
47 toggle_disabled,
48 ),
49 )
50 .run();
51}
52
53const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
54const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
55const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
56const SLIDER_TRACK: Color = Color::srgb(0.05, 0.05, 0.05);
57const SLIDER_THUMB: Color = Color::srgb(0.35, 0.75, 0.35);
58const ELEMENT_OUTLINE: Color = Color::srgb(0.45, 0.45, 0.45);
59const ELEMENT_FILL: Color = Color::srgb(0.35, 0.75, 0.35);
60const ELEMENT_FILL_DISABLED: Color = Color::srgb(0.5019608, 0.5019608, 0.5019608);
61
62#[derive(Component)]
64struct DemoButton;
65
66#[derive(Component, Default)]
68struct DemoSlider;
69
70#[derive(Component, Default)]
72struct DemoSliderThumb;
73
74#[derive(Component, Default)]
76struct DemoCheckbox;
77
78#[derive(Component, Default)]
81struct DemoRadio(TrackClick);
82
83#[derive(Component)]
85struct DemoMenuAnchor;
86
87#[derive(Component)]
89struct DemoMenuButton;
90
91#[derive(Component)]
93struct DemoMenuItem;
94
95#[derive(Resource)]
102struct DemoWidgetStates {
103 slider_value: f32,
104 slider_click: TrackClick,
105}
106
107fn update_widget_values(
109 res: Res<DemoWidgetStates>,
110 mut sliders: Query<(Entity, &mut Slider), With<DemoSlider>>,
111 radios: Query<(Entity, &DemoRadio, Has<Checked>)>,
112 mut commands: Commands,
113) {
114 if res.is_changed() {
115 for (slider_ent, mut slider) in sliders.iter_mut() {
116 commands
117 .entity(slider_ent)
118 .insert(SliderValue(res.slider_value));
119 slider.track_click = res.slider_click;
120 }
121
122 for (radio_id, radio_value, checked) in radios.iter() {
123 let will_be_checked = radio_value.0 == res.slider_click;
124 if will_be_checked != checked {
125 if will_be_checked {
126 commands.entity(radio_id).insert(Checked);
127 } else {
128 commands.entity(radio_id).remove::<Checked>();
129 }
130 }
131 }
132 }
133}
134
135fn setup(mut commands: Commands, assets: Res<AssetServer>) {
136 commands.spawn(Camera2d);
138 commands.spawn(demo_root(&assets));
139}
140
141fn demo_root(asset_server: &AssetServer) -> impl Bundle {
142 (
143 Node {
144 width: percent(100),
145 height: percent(100),
146 align_items: AlignItems::Center,
147 justify_content: JustifyContent::Center,
148 display: Display::Flex,
149 flex_direction: FlexDirection::Column,
150 row_gap: px(10),
151 ..default()
152 },
153 TabGroup::default(),
154 children![
155 (
156 button(asset_server),
157 observe(|_activate: On<Activate>| {
158 info!("Button clicked!");
159 }),
160 ),
161 (
162 slider(0.0, 100.0, 50.0),
163 observe(
164 |value_change: On<ValueChange<f32>>,
165 mut widget_states: ResMut<DemoWidgetStates>| {
166 widget_states.slider_value = value_change.value;
167 },
168 )
169 ),
170 (
171 checkbox(asset_server, "Checkbox"),
172 observe(checkbox_self_update)
173 ),
174 (
175 radio_group(asset_server),
176 observe(
177 |value_change: On<ValueChange<Entity>>,
178 mut widget_states: ResMut<DemoWidgetStates>,
179 q_radios: Query<&DemoRadio>| {
180 if let Ok(radio) = q_radios.get(value_change.value) {
181 widget_states.slider_click = radio.0;
182 }
183 },
184 )
185 ),
186 menu_button(asset_server),
187 Text::new("Press 'D' to toggle widget disabled states"),
188 ],
189 )
190}
191
192fn button(asset_server: &AssetServer) -> impl Bundle {
193 (
194 Node {
195 width: px(150),
196 height: px(65),
197 border: UiRect::all(px(5)),
198 border_radius: BorderRadius::MAX,
199 justify_content: JustifyContent::Center,
200 align_items: AlignItems::Center,
201 ..default()
202 },
203 DemoButton,
204 Button,
205 Hovered::default(),
206 TabIndex(0),
207 BorderColor::all(Color::BLACK),
208 BackgroundColor(NORMAL_BUTTON),
209 children![(
210 Text::new("Button"),
211 TextFont {
212 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
213 font_size: FontSize::Px(33.0),
214 ..default()
215 },
216 TextColor(Color::srgb(0.9, 0.9, 0.9)),
217 TextShadow::default(),
218 )],
219 )
220}
221
222fn menu_button(asset_server: &AssetServer) -> impl Bundle {
223 (
224 Node { ..default() },
225 DemoMenuAnchor,
226 observe(on_menu_event),
227 children![(
228 Node {
229 width: px(200),
230 height: px(65),
231 border: UiRect::all(px(5)),
232 box_sizing: BoxSizing::BorderBox,
233 justify_content: JustifyContent::SpaceBetween,
234 align_items: AlignItems::Center,
235 padding: UiRect::axes(px(16), px(0)),
236 border_radius: BorderRadius::all(px(5)),
237 ..default()
238 },
239 DemoMenuButton,
240 Button,
241 MenuButton,
242 Hovered::default(),
243 TabIndex(0),
244 BorderColor::all(Color::BLACK),
245 BackgroundColor(NORMAL_BUTTON),
246 children![
247 (
248 Text::new("Menu"),
249 TextFont {
250 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
251 font_size: FontSize::Px(33.0),
252 ..default()
253 },
254 TextColor(Color::srgb(0.9, 0.9, 0.9)),
255 TextShadow::default(),
256 ),
257 (
258 Node {
259 width: px(12),
260 height: px(12),
261 ..default()
262 },
263 BackgroundColor(GRAY.into()),
264 )
265 ],
266 )],
267 )
268}
269
270fn update_button_style(
271 mut buttons: Query<
272 (
273 Has<Pressed>,
274 &Hovered,
275 Has<InteractionDisabled>,
276 &mut BackgroundColor,
277 &mut BorderColor,
278 &Children,
279 ),
280 (
281 Or<(
282 Changed<Pressed>,
283 Changed<Hovered>,
284 Added<InteractionDisabled>,
285 )>,
286 With<DemoButton>,
287 ),
288 >,
289 mut text_query: Query<&mut Text>,
290) {
291 for (pressed, hovered, disabled, mut color, mut border_color, children) in &mut buttons {
292 let mut text = text_query.get_mut(children[0]).unwrap();
293 set_button_style(
294 disabled,
295 hovered.get(),
296 pressed,
297 &mut color,
298 &mut border_color,
299 &mut text,
300 );
301 }
302}
303
304fn update_button_style2(
306 mut buttons: Query<
307 (
308 Has<Pressed>,
309 &Hovered,
310 Has<InteractionDisabled>,
311 &mut BackgroundColor,
312 &mut BorderColor,
313 &Children,
314 ),
315 With<DemoButton>,
316 >,
317 mut removed_depressed: RemovedComponents<Pressed>,
318 mut removed_disabled: RemovedComponents<InteractionDisabled>,
319 mut text_query: Query<&mut Text>,
320) {
321 removed_depressed
322 .read()
323 .chain(removed_disabled.read())
324 .for_each(|entity| {
325 if let Ok((pressed, hovered, disabled, mut color, mut border_color, children)) =
326 buttons.get_mut(entity)
327 {
328 let mut text = text_query.get_mut(children[0]).unwrap();
329 set_button_style(
330 disabled,
331 hovered.get(),
332 pressed,
333 &mut color,
334 &mut border_color,
335 &mut text,
336 );
337 }
338 });
339}
340
341fn set_button_style(
342 disabled: bool,
343 hovered: bool,
344 pressed: bool,
345 color: &mut BackgroundColor,
346 border_color: &mut BorderColor,
347 text: &mut Text,
348) {
349 match (disabled, hovered, pressed) {
350 (true, _, _) => {
352 **text = "Disabled".to_string();
353 *color = NORMAL_BUTTON.into();
354 border_color.set_all(GRAY);
355 }
356
357 (false, true, true) => {
359 **text = "Press".to_string();
360 *color = PRESSED_BUTTON.into();
361 border_color.set_all(RED);
362 }
363
364 (false, true, false) => {
366 **text = "Hover".to_string();
367 *color = HOVERED_BUTTON.into();
368 border_color.set_all(WHITE);
369 }
370
371 (false, false, _) => {
373 **text = "Button".to_string();
374 *color = NORMAL_BUTTON.into();
375 border_color.set_all(BLACK);
376 }
377 }
378}
379
380fn slider(min: f32, max: f32, value: f32) -> impl Bundle {
382 (
383 Node {
384 display: Display::Flex,
385 flex_direction: FlexDirection::Column,
386 justify_content: JustifyContent::Center,
387 align_items: AlignItems::Stretch,
388 justify_items: JustifyItems::Center,
389 column_gap: px(4),
390 height: px(12),
391 width: percent(30),
392 ..default()
393 },
394 Name::new("Slider"),
395 Hovered::default(),
396 DemoSlider,
397 Slider {
398 track_click: TrackClick::Snap,
399 ..Default::default()
400 },
401 SliderValue(value),
402 SliderRange::new(min, max),
403 TabIndex(0),
404 Children::spawn((
405 Spawn((
407 Node {
408 height: px(6),
409 border_radius: BorderRadius::all(px(3)),
410 ..default()
411 },
412 BackgroundColor(SLIDER_TRACK), )),
414 Spawn((
418 Node {
419 display: Display::Flex,
420 position_type: PositionType::Absolute,
421 left: px(0),
422 right: px(12),
424 top: px(0),
425 bottom: px(0),
426 ..default()
427 },
428 children![(
429 DemoSliderThumb,
431 SliderThumb,
432 Node {
433 display: Display::Flex,
434 width: px(12),
435 height: px(12),
436 position_type: PositionType::Absolute,
437 left: percent(0), border_radius: BorderRadius::MAX,
439 ..default()
440 },
441 BackgroundColor(SLIDER_THUMB),
442 )],
443 )),
444 )),
445 )
446}
447
448fn update_slider_style(
450 sliders: Query<
451 (
452 Entity,
453 &SliderValue,
454 &SliderRange,
455 &Hovered,
456 &SliderDragState,
457 Has<InteractionDisabled>,
458 ),
459 (
460 Or<(
461 Changed<SliderValue>,
462 Changed<SliderRange>,
463 Changed<Hovered>,
464 Changed<SliderDragState>,
465 Added<InteractionDisabled>,
466 )>,
467 With<DemoSlider>,
468 ),
469 >,
470 children: Query<&Children>,
471 mut thumbs: Query<(&mut Node, &mut BackgroundColor, Has<DemoSliderThumb>), Without<DemoSlider>>,
472) {
473 for (slider_ent, value, range, hovered, drag_state, disabled) in sliders.iter() {
474 for child in children.iter_descendants(slider_ent) {
475 if let Ok((mut thumb_node, mut thumb_bg, is_thumb)) = thumbs.get_mut(child)
476 && is_thumb
477 {
478 thumb_node.left = percent(range.thumb_position(value.0) * 100.0);
479 thumb_bg.0 = thumb_color(disabled, hovered.0 | drag_state.dragging);
480 }
481 }
482 }
483}
484
485fn update_slider_style2(
486 sliders: Query<
487 (Entity, &Hovered, &SliderDragState, Has<InteractionDisabled>),
488 With<DemoSlider>,
489 >,
490 children: Query<&Children>,
491 mut thumbs: Query<(&mut BackgroundColor, Has<DemoSliderThumb>), Without<DemoSlider>>,
492 mut removed_disabled: RemovedComponents<InteractionDisabled>,
493) {
494 removed_disabled.read().for_each(|entity| {
495 if let Ok((slider_ent, hovered, drag_state, disabled)) = sliders.get(entity) {
496 for child in children.iter_descendants(slider_ent) {
497 if let Ok((mut thumb_bg, is_thumb)) = thumbs.get_mut(child)
498 && is_thumb
499 {
500 thumb_bg.0 = thumb_color(disabled, hovered.0 | drag_state.dragging);
501 }
502 }
503 }
504 });
505}
506
507fn thumb_color(disabled: bool, hovered: bool) -> Color {
508 match (disabled, hovered) {
509 (true, _) => ELEMENT_FILL_DISABLED,
510
511 (false, true) => SLIDER_THUMB.lighter(0.3),
512
513 _ => SLIDER_THUMB,
514 }
515}
516
517fn checkbox(asset_server: &AssetServer, caption: &str) -> impl Bundle {
519 (
520 Node {
521 display: Display::Flex,
522 flex_direction: FlexDirection::Row,
523 justify_content: JustifyContent::FlexStart,
524 align_items: AlignItems::Center,
525 align_content: AlignContent::Center,
526 column_gap: px(4),
527 ..default()
528 },
529 Name::new("Checkbox"),
530 Hovered::default(),
531 DemoCheckbox,
532 Checkbox,
533 TabIndex(0),
534 Children::spawn((
535 Spawn((
536 Node {
538 display: Display::Flex,
539 width: px(16),
540 height: px(16),
541 border: UiRect::all(px(2)),
542 border_radius: BorderRadius::all(px(3)),
543 ..default()
544 },
545 BorderColor::all(ELEMENT_OUTLINE), children![
547 (
549 Node {
550 display: Display::Flex,
551 width: px(8),
552 height: px(8),
553 position_type: PositionType::Absolute,
554 left: px(2),
555 top: px(2),
556 ..default()
557 },
558 BackgroundColor(ELEMENT_FILL),
559 ),
560 ],
561 )),
562 Spawn((
563 Text::new(caption),
564 TextFont {
565 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
566 font_size: FontSize::Px(20.0),
567 ..default()
568 },
569 )),
570 )),
571 )
572}
573
574fn update_checkbox_or_radio_style(
576 mut q_checkbox: Query<
577 (Has<Checked>, &Hovered, Has<InteractionDisabled>, &Children),
578 (
579 Or<(With<DemoCheckbox>, With<DemoRadio>)>,
580 Or<(
581 Added<DemoCheckbox>,
582 Changed<Hovered>,
583 Added<Checked>,
584 Added<InteractionDisabled>,
585 )>,
586 ),
587 >,
588 mut q_border_color: Query<
589 (&mut BorderColor, &mut Children),
590 (Without<DemoCheckbox>, Without<DemoRadio>),
591 >,
592 mut q_bg_color: Query<&mut BackgroundColor, (Without<DemoCheckbox>, Without<Children>)>,
593) {
594 for (checked, Hovered(is_hovering), is_disabled, children) in q_checkbox.iter_mut() {
595 let Some(border_id) = children.first() else {
596 continue;
597 };
598
599 let Ok((mut border_color, border_children)) = q_border_color.get_mut(*border_id) else {
600 continue;
601 };
602
603 let Some(mark_id) = border_children.first() else {
604 warn!("Checkbox does not have a mark entity.");
605 continue;
606 };
607
608 let Ok(mut mark_bg) = q_bg_color.get_mut(*mark_id) else {
609 warn!("Checkbox mark entity lacking a background color.");
610 continue;
611 };
612
613 set_checkbox_or_radio_style(
614 is_disabled,
615 *is_hovering,
616 checked,
617 &mut border_color,
618 &mut mark_bg,
619 );
620 }
621}
622
623fn update_checkbox_or_radio_style2(
624 mut q_checkbox: Query<
625 (Has<Checked>, &Hovered, Has<InteractionDisabled>, &Children),
626 Or<(With<DemoCheckbox>, With<DemoRadio>)>,
627 >,
628 mut q_border_color: Query<
629 (&mut BorderColor, &mut Children),
630 (Without<DemoCheckbox>, Without<DemoRadio>),
631 >,
632 mut q_bg_color: Query<
633 &mut BackgroundColor,
634 (Without<DemoCheckbox>, Without<DemoRadio>, Without<Children>),
635 >,
636 mut removed_checked: RemovedComponents<Checked>,
637 mut removed_disabled: RemovedComponents<InteractionDisabled>,
638) {
639 removed_checked
640 .read()
641 .chain(removed_disabled.read())
642 .for_each(|entity| {
643 if let Ok((checked, Hovered(is_hovering), is_disabled, children)) =
644 q_checkbox.get_mut(entity)
645 {
646 let Some(border_id) = children.first() else {
647 return;
648 };
649
650 let Ok((mut border_color, border_children)) = q_border_color.get_mut(*border_id)
651 else {
652 return;
653 };
654
655 let Some(mark_id) = border_children.first() else {
656 warn!("Checkbox does not have a mark entity.");
657 return;
658 };
659
660 let Ok(mut mark_bg) = q_bg_color.get_mut(*mark_id) else {
661 warn!("Checkbox mark entity lacking a background color.");
662 return;
663 };
664
665 set_checkbox_or_radio_style(
666 is_disabled,
667 *is_hovering,
668 checked,
669 &mut border_color,
670 &mut mark_bg,
671 );
672 }
673 });
674}
675
676fn set_checkbox_or_radio_style(
677 disabled: bool,
678 hovering: bool,
679 checked: bool,
680 border_color: &mut BorderColor,
681 mark_bg: &mut BackgroundColor,
682) {
683 let color: Color = if disabled {
684 ELEMENT_OUTLINE.with_alpha(0.2)
686 } else if hovering {
687 ELEMENT_OUTLINE.lighter(0.2)
689 } else {
690 ELEMENT_OUTLINE
692 };
693
694 border_color.set_all(color);
696
697 let mark_color: Color = match (disabled, checked) {
698 (true, true) => ELEMENT_FILL_DISABLED,
699 (false, true) => ELEMENT_FILL,
700 (_, false) => Srgba::NONE.into(),
701 };
702
703 if mark_bg.0 != mark_color {
704 mark_bg.0 = mark_color;
706 }
707}
708
709fn radio_group(asset_server: &AssetServer) -> impl Bundle {
711 (
712 Node {
713 display: Display::Flex,
714 flex_direction: FlexDirection::Column,
715 align_items: AlignItems::Start,
716 column_gap: px(4),
717 ..default()
718 },
719 Name::new("RadioGroup"),
720 RadioGroup,
721 TabIndex::default(),
722 children![
723 (radio(asset_server, TrackClick::Drag, "Slider Drag"),),
724 (radio(asset_server, TrackClick::Step, "Slider Step"),),
725 (radio(asset_server, TrackClick::Snap, "Slider Snap"),)
726 ],
727 )
728}
729
730fn radio(asset_server: &AssetServer, value: TrackClick, caption: &str) -> impl Bundle {
732 (
733 Node {
734 display: Display::Flex,
735 flex_direction: FlexDirection::Row,
736 justify_content: JustifyContent::FlexStart,
737 align_items: AlignItems::Center,
738 align_content: AlignContent::Center,
739 column_gap: px(4),
740 ..default()
741 },
742 Name::new("RadioButton"),
743 Hovered::default(),
744 DemoRadio(value),
745 RadioButton,
746 Children::spawn((
747 Spawn((
748 Node {
750 display: Display::Flex,
751 width: px(16),
752 height: px(16),
753 border: UiRect::all(px(2)),
754 border_radius: BorderRadius::MAX,
755 ..default()
756 },
757 BorderColor::all(ELEMENT_OUTLINE), children![
759 (
761 Node {
762 display: Display::Flex,
763 width: px(8),
764 height: px(8),
765 position_type: PositionType::Absolute,
766 left: px(2),
767 top: px(2),
768 border_radius: BorderRadius::MAX,
769 ..default()
770 },
771 BackgroundColor(ELEMENT_FILL),
772 ),
773 ],
774 )),
775 Spawn((
776 Text::new(caption),
777 TextFont {
778 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
779 font_size: FontSize::Px(20.0),
780 ..default()
781 },
782 )),
783 )),
784 )
785}
786
787fn on_menu_event(
788 menu_event: On<MenuEvent>,
789 q_anchor: Single<(Entity, &Children), With<DemoMenuAnchor>>,
790 q_popup: Query<Entity, With<MenuPopup>>,
791 assets: Res<AssetServer>,
792 mut focus: ResMut<InputFocus>,
793 mut commands: Commands,
794) {
795 let (anchor, children) = q_anchor.into_inner();
796 let popup = children.iter().find_map(|c| q_popup.get(c).ok());
797 info!("Menu action: {:?}", menu_event.action);
798 match menu_event.action {
799 MenuAction::Open(_) => {
800 if popup.is_none() {
801 spawn_menu(anchor, assets, commands);
802 }
803 }
804 MenuAction::Toggle => match popup {
805 Some(popup) => commands.entity(popup).despawn(),
806 None => spawn_menu(anchor, assets, commands),
807 },
808 MenuAction::CloseAll => {
809 if let Some(popup) = popup {
810 commands.entity(popup).despawn();
811 }
812 }
813 MenuAction::FocusRoot => {
814 focus.set(anchor, FocusCause::Navigated);
815 }
816 }
817}
818
819fn spawn_menu(anchor: Entity, assets: Res<AssetServer>, mut commands: Commands) {
820 let menu = commands
821 .spawn((
822 Node {
823 display: Display::Flex,
824 flex_direction: FlexDirection::Column,
825 min_height: px(10.),
826 min_width: percent(100),
827 border: UiRect::all(px(1)),
828 position_type: PositionType::Absolute,
829 ..default()
830 },
831 MenuPopup::default(),
832 BorderColor::all(GREEN),
833 BackgroundColor(GRAY.into()),
834 BoxShadow::new(
835 Srgba::BLACK.with_alpha(0.9).into(),
836 px(0),
837 px(0),
838 px(1),
839 px(4),
840 ),
841 GlobalZIndex(100),
842 Popover {
843 positions: vec![
844 PopoverPlacement {
845 side: PopoverSide::Bottom,
846 align: PopoverAlign::Start,
847 gap: 2.0,
848 },
849 PopoverPlacement {
850 side: PopoverSide::Top,
851 align: PopoverAlign::Start,
852 gap: 2.0,
853 },
854 ],
855 window_margin: 10.0,
856 },
857 OverrideClip,
858 children![
859 menu_item(&assets),
860 menu_item(&assets),
861 menu_item(&assets),
862 menu_item(&assets)
863 ],
864 ))
865 .id();
866 commands.entity(anchor).add_child(menu);
867}
868
869fn menu_item(asset_server: &AssetServer) -> impl Bundle {
870 (
871 Node {
872 padding: UiRect::axes(px(8), px(2)),
873 justify_content: JustifyContent::Center,
874 align_items: AlignItems::Start,
875 ..default()
876 },
877 DemoMenuItem,
878 MenuItem,
879 Hovered::default(),
880 TabIndex(0),
881 BackgroundColor(NORMAL_BUTTON),
882 children![(
883 Text::new("Menu Item"),
884 TextFont {
885 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
886 font_size: FontSize::Px(33.0),
887 ..default()
888 },
889 TextColor(Color::srgb(0.9, 0.9, 0.9)),
890 TextShadow::default(),
891 )],
892 )
893}
894
895fn update_menu_item_style(
896 mut buttons: Query<
897 (
898 Has<Pressed>,
899 &Hovered,
900 Has<InteractionDisabled>,
901 &mut BackgroundColor,
902 ),
903 (
904 Or<(
905 Changed<Pressed>,
906 Changed<Hovered>,
907 Added<InteractionDisabled>,
908 )>,
909 With<DemoMenuItem>,
910 ),
911 >,
912) {
913 for (pressed, hovered, disabled, mut color) in &mut buttons {
914 set_menu_item_style(disabled, hovered.get(), pressed, &mut color);
915 }
916}
917
918fn update_menu_item_style2(
920 mut buttons: Query<
921 (
922 Has<Pressed>,
923 &Hovered,
924 Has<InteractionDisabled>,
925 &mut BackgroundColor,
926 ),
927 With<DemoMenuItem>,
928 >,
929 mut removed_depressed: RemovedComponents<Pressed>,
930 mut removed_disabled: RemovedComponents<InteractionDisabled>,
931) {
932 removed_depressed
933 .read()
934 .chain(removed_disabled.read())
935 .for_each(|entity| {
936 if let Ok((pressed, hovered, disabled, mut color)) = buttons.get_mut(entity) {
937 set_menu_item_style(disabled, hovered.get(), pressed, &mut color);
938 }
939 });
940}
941
942fn set_menu_item_style(disabled: bool, hovered: bool, pressed: bool, color: &mut BackgroundColor) {
943 match (disabled, hovered, pressed) {
944 (false, true, true) => {
946 *color = PRESSED_BUTTON.into();
947 }
948
949 (false, true, false) => {
951 *color = HOVERED_BUTTON.into();
952 }
953
954 _ => {
956 *color = NORMAL_BUTTON.into();
957 }
958 }
959}
960
961fn toggle_disabled(
962 input: Res<ButtonInput<KeyCode>>,
963 mut interaction_query: Query<
964 (Entity, Has<InteractionDisabled>),
965 Or<(
966 With<Button>,
967 With<MenuButton>,
968 With<Slider>,
969 With<Checkbox>,
970 With<RadioButton>,
971 )>,
972 >,
973 mut commands: Commands,
974) {
975 if input.just_pressed(KeyCode::KeyD) {
976 for (entity, disabled) in &mut interaction_query {
977 if disabled {
978 info!("Widget enabled");
979 commands.entity(entity).remove::<InteractionDisabled>();
980 } else {
981 info!("Widget disabled");
982 commands.entity(entity).insert(InteractionDisabled);
983 }
984 }
985 }
986}