tab_navigation/
tab_navigation.rs

1//! This example illustrates the use of tab navigation.
2
3use bevy::{
4    color::palettes::basic::*,
5    input_focus::{
6        tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
7        InputDispatchPlugin, InputFocus,
8    },
9    prelude::*,
10    winit::WinitSettings,
11};
12
13fn main() {
14    App::new()
15        .add_plugins((DefaultPlugins, InputDispatchPlugin, TabNavigationPlugin))
16        // Only run the app when there is user input. This will significantly reduce CPU/GPU use.
17        .insert_resource(WinitSettings::desktop_app())
18        .add_systems(Startup, setup)
19        .add_systems(Update, (button_system, focus_system))
20        .run();
21}
22
23const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
24const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
25const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
26
27fn button_system(
28    mut interaction_query: Query<
29        (
30            &Interaction,
31            &mut BackgroundColor,
32            &mut BorderColor,
33            &Children,
34        ),
35        (Changed<Interaction>, With<Button>),
36    >,
37    mut text_query: Query<&mut Text>,
38) {
39    for (interaction, mut color, mut border_color, children) in &mut interaction_query {
40        let mut text = text_query.get_mut(children[0]).unwrap();
41        match *interaction {
42            Interaction::Pressed => {
43                **text = "Press".to_string();
44                *color = PRESSED_BUTTON.into();
45                border_color.0 = RED.into();
46            }
47            Interaction::Hovered => {
48                **text = "Hover".to_string();
49                *color = HOVERED_BUTTON.into();
50                border_color.0 = Color::WHITE;
51            }
52            Interaction::None => {
53                **text = "Button".to_string();
54                *color = NORMAL_BUTTON.into();
55                border_color.0 = Color::BLACK;
56            }
57        }
58    }
59}
60
61fn focus_system(
62    mut commands: Commands,
63    focus: Res<InputFocus>,
64    mut query: Query<Entity, With<Button>>,
65) {
66    if focus.is_changed() {
67        for button in query.iter_mut() {
68            if focus.0 == Some(button) {
69                commands.entity(button).insert(Outline {
70                    color: Color::WHITE,
71                    width: Val::Px(2.0),
72                    offset: Val::Px(2.0),
73                });
74            } else {
75                commands.entity(button).remove::<Outline>();
76            }
77        }
78    }
79}
80
81fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
82    // ui camera
83    commands.spawn(Camera2d);
84    commands
85        .spawn(Node {
86            width: Val::Percent(100.0),
87            height: Val::Percent(100.0),
88            display: Display::Flex,
89            flex_direction: FlexDirection::Column,
90            align_items: AlignItems::Center,
91            justify_content: JustifyContent::Center,
92            ..default()
93        })
94        .observe(
95            |mut trigger: Trigger<Pointer<Click>>, mut focus: ResMut<InputFocus>| {
96                focus.0 = None;
97                trigger.propagate(false);
98            },
99        )
100        .with_children(|parent| {
101            parent.spawn(Text::new("Tab Group 0"));
102            parent
103                .spawn((
104                    Node {
105                        display: Display::Flex,
106                        flex_direction: FlexDirection::Row,
107                        column_gap: Val::Px(6.0),
108                        margin: UiRect {
109                            bottom: Val::Px(10.0),
110                            ..default()
111                        },
112                        ..default()
113                    },
114                    TabGroup::new(0),
115                ))
116                .with_children(|parent| {
117                    create_button(parent, &asset_server);
118                    create_button(parent, &asset_server);
119                    create_button(parent, &asset_server);
120                    create_button(parent, &asset_server);
121                });
122
123            parent.spawn(Text::new("Tab Group 2"));
124            parent
125                .spawn((
126                    Node {
127                        display: Display::Flex,
128                        flex_direction: FlexDirection::Row,
129                        column_gap: Val::Px(6.0),
130                        margin: UiRect {
131                            bottom: Val::Px(10.0),
132                            ..default()
133                        },
134                        ..default()
135                    },
136                    TabGroup::new(2),
137                ))
138                .with_children(|parent| {
139                    create_button(parent, &asset_server);
140                    create_button(parent, &asset_server);
141                    create_button(parent, &asset_server);
142                    create_button(parent, &asset_server);
143                });
144
145            parent.spawn(Text::new("Tab Group 1"));
146            parent
147                .spawn((
148                    Node {
149                        display: Display::Flex,
150                        flex_direction: FlexDirection::Row,
151                        column_gap: Val::Px(6.0),
152                        margin: UiRect {
153                            bottom: Val::Px(10.0),
154                            ..default()
155                        },
156                        ..default()
157                    },
158                    TabGroup::new(1),
159                ))
160                .with_children(|parent| {
161                    create_button(parent, &asset_server);
162                    create_button(parent, &asset_server);
163                    create_button(parent, &asset_server);
164                    create_button(parent, &asset_server);
165                });
166
167            parent.spawn(Text::new("Modal Tab Group"));
168            parent
169                .spawn((
170                    Node {
171                        display: Display::Flex,
172                        flex_direction: FlexDirection::Row,
173                        column_gap: Val::Px(6.0),
174                        ..default()
175                    },
176                    TabGroup::modal(),
177                ))
178                .with_children(|parent| {
179                    create_button(parent, &asset_server);
180                    create_button(parent, &asset_server);
181                    create_button(parent, &asset_server);
182                    create_button(parent, &asset_server);
183                });
184        });
185}
186
187fn create_button(parent: &mut ChildSpawnerCommands<'_>, asset_server: &AssetServer) {
188    parent
189        .spawn((
190            Button,
191            Node {
192                width: Val::Px(150.0),
193                height: Val::Px(65.0),
194                border: UiRect::all(Val::Px(5.0)),
195                // horizontally center child text
196                justify_content: JustifyContent::Center,
197                // vertically center child text
198                align_items: AlignItems::Center,
199                ..default()
200            },
201            BorderColor(Color::BLACK),
202            BorderRadius::MAX,
203            BackgroundColor(NORMAL_BUTTON),
204            TabIndex(0),
205        ))
206        .observe(
207            |mut trigger: Trigger<Pointer<Click>>, mut focus: ResMut<InputFocus>| {
208                focus.0 = Some(trigger.target());
209                trigger.propagate(false);
210            },
211        )
212        .with_child((
213            Text::new("Button"),
214            TextFont {
215                font: asset_server.load("fonts/FiraSans-Bold.ttf"),
216                font_size: 23.0,
217                ..default()
218            },
219            TextColor(Color::srgb(0.9, 0.9, 0.9)),
220        ));
221}