1use bevy::{
4 color::palettes::basic::*,
5 input_focus::{
6 tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
7 FocusCause, InputFocus,
8 },
9 picking::hover::Hovered,
10 platform::collections::HashSet,
11 prelude::*,
12 ui::Pressed,
13 ui_widgets::Button,
14};
15
16fn main() {
17 App::new()
18 .add_plugins((DefaultPlugins, TabNavigationPlugin))
19 .add_systems(Startup, setup)
20 .add_systems(Update, (focus_system, button_styles))
21 .run();
22}
23
24fn focus_system(
25 mut commands: Commands,
26 focus: Res<InputFocus>,
27 mut query: Query<Entity, With<Button>>,
28) {
29 if focus.is_changed() {
30 for button in query.iter_mut() {
31 if focus.get() == Some(button) {
32 commands.entity(button).insert(Outline {
33 color: Color::WHITE,
34 width: px(2),
35 offset: px(2),
36 });
37 } else {
38 commands.entity(button).remove::<Outline>();
39 }
40 }
41 }
42}
43
44fn setup(mut commands: Commands) {
45 commands.spawn(Camera2d);
47 commands
48 .spawn(Node {
49 width: percent(100),
50 height: percent(100),
51 display: Display::Flex,
52 flex_direction: FlexDirection::Column,
53 align_items: AlignItems::Center,
54 justify_content: JustifyContent::Center,
55 row_gap: px(6),
56 ..default()
57 })
58 .observe(
59 |mut event: On<PointerClick>, mut focus: ResMut<InputFocus>| {
60 focus.clear();
61 event.propagate(false);
62 },
63 )
64 .with_children(|parent| {
65 for (label, tab_group, indices) in [
66 ("TabGroup 0", TabGroup::new(0), [0, 0, 0, 0]),
68 ("TabGroup 2", TabGroup::new(2), [3, 2, 1, 0]),
70 ("TabGroup 1", TabGroup::new(1), [0, 1, 2, 3]),
72 ("Modal TabGroup", TabGroup::modal(), [0, 3, 1, 2]),
74 ] {
75 parent.spawn(Text::new(label));
76 parent
77 .spawn((
78 Node {
79 display: Display::Flex,
80 flex_direction: FlexDirection::Row,
81 column_gap: px(6),
82 margin: UiRect {
83 bottom: px(10),
84 ..default()
85 },
86 ..default()
87 },
88 tab_group,
89 ))
90 .with_children(|parent| {
91 for i in indices {
92 parent
93 .spawn((
94 Button,
95 Hovered::default(),
96 Node {
97 width: px(200),
98 height: px(65),
99 border: UiRect::all(px(5)),
100 justify_content: JustifyContent::Center,
101 align_items: AlignItems::Center,
102 ..default()
103 },
104 BorderColor::all(Color::BLACK),
105 BackgroundColor(NORMAL_BUTTON),
106 TabIndex(i),
107 children![(
108 Text::new(format!("TabIndex {i}")),
109 TextFont {
110 font_size: FontSize::Px(20.0),
111 ..default()
112 },
113 TextColor(Color::srgb(0.9, 0.9, 0.9)),
114 )],
115 ))
116 .observe(
117 |mut click: On<PointerClick>, mut focus: ResMut<InputFocus>| {
118 focus.set(click.entity, FocusCause::Pressed);
119 click.propagate(false);
120 },
121 );
122 }
123 });
124 }
125 });
126}
127
128const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
129const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
130const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
131
132fn button_styles(
134 mut buttons: Query<
135 (
136 Entity,
137 Option<Ref<Pressed>>,
138 Ref<Hovered>,
139 &mut BackgroundColor,
140 &mut BorderColor,
141 ),
142 With<Button>,
143 >,
144 mut removed_pressed: RemovedComponents<Pressed>,
145) {
146 let just_unpressed: HashSet<Entity> = removed_pressed.read().collect();
147 for (entity, pressed, hovered, mut background_color, mut border_color) in &mut buttons {
148 let changed = hovered.is_changed()
149 || pressed.as_ref().is_some_and(Ref::is_changed)
150 || just_unpressed.contains(&entity);
151 if changed {
152 match (pressed.is_some(), hovered.get()) {
153 (true, _) => {
154 *background_color = BackgroundColor(PRESSED_BUTTON);
155 *border_color = BorderColor::all(RED);
156 }
157 (false, true) => {
158 *background_color = BackgroundColor(HOVERED_BUTTON);
159 *border_color = BorderColor::all(WHITE);
160 }
161 (false, false) => {
162 *background_color = BackgroundColor(NORMAL_BUTTON);
163 *border_color = BorderColor::all(BLACK);
164 }
165 }
166 }
167 }
168}