Skip to main content

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};
11
12fn main() {
13    App::new()
14        .add_plugins((DefaultPlugins, InputDispatchPlugin, TabNavigationPlugin))
15        .add_systems(Startup, setup)
16        .add_systems(Update, (button_system, focus_system))
17        .run();
18}
19
20const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
21const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
22const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
23
24fn button_system(
25    mut interaction_query: Query<
26        (&Interaction, &mut BackgroundColor, &mut BorderColor),
27        (Changed<Interaction>, With<Button>),
28    >,
29) {
30    for (interaction, mut color, mut border_color) in &mut interaction_query {
31        match *interaction {
32            Interaction::Pressed => {
33                *color = PRESSED_BUTTON.into();
34                *border_color = BorderColor::all(RED);
35            }
36            Interaction::Hovered => {
37                *color = HOVERED_BUTTON.into();
38                *border_color = BorderColor::all(Color::WHITE);
39            }
40            Interaction::None => {
41                *color = NORMAL_BUTTON.into();
42                *border_color = BorderColor::all(Color::BLACK);
43            }
44        }
45    }
46}
47
48fn focus_system(
49    mut commands: Commands,
50    focus: Res<InputFocus>,
51    mut query: Query<Entity, With<Button>>,
52) {
53    if focus.is_changed() {
54        for button in query.iter_mut() {
55            if focus.0 == Some(button) {
56                commands.entity(button).insert(Outline {
57                    color: Color::WHITE,
58                    width: px(2),
59                    offset: px(2),
60                });
61            } else {
62                commands.entity(button).remove::<Outline>();
63            }
64        }
65    }
66}
67
68fn setup(mut commands: Commands) {
69    // ui camera
70    commands.spawn(Camera2d);
71    commands
72        .spawn(Node {
73            width: percent(100),
74            height: percent(100),
75            display: Display::Flex,
76            flex_direction: FlexDirection::Column,
77            align_items: AlignItems::Center,
78            justify_content: JustifyContent::Center,
79            row_gap: px(6),
80            ..default()
81        })
82        .observe(
83            |mut event: On<Pointer<Click>>, mut focus: ResMut<InputFocus>| {
84                focus.0 = None;
85                event.propagate(false);
86            },
87        )
88        .with_children(|parent| {
89            for (label, tab_group, indices) in [
90                // In this group all the buttons have the same `TabIndex` so they will be visited according to their order as children.
91                ("TabGroup 0", TabGroup::new(0), [0, 0, 0, 0]),
92                // In this group the `TabIndex`s are reversed so the buttons will be visited in right-to-left order.
93                ("TabGroup 2", TabGroup::new(2), [3, 2, 1, 0]),
94                // In this group the orders of the indices and buttons match so the buttons will be visited in left-to-right order.
95                ("TabGroup 1", TabGroup::new(1), [0, 1, 2, 3]),
96                // Visit the modal group's buttons in an arbitrary order.
97                ("Modal TabGroup", TabGroup::modal(), [0, 3, 1, 2]),
98            ] {
99                parent.spawn(Text::new(label));
100                parent
101                    .spawn((
102                        Node {
103                            display: Display::Flex,
104                            flex_direction: FlexDirection::Row,
105                            column_gap: px(6),
106                            margin: UiRect {
107                                bottom: px(10),
108                                ..default()
109                            },
110                            ..default()
111                        },
112                        tab_group,
113                    ))
114                    .with_children(|parent| {
115                        for i in indices {
116                            parent
117                                .spawn((
118                                    Button,
119                                    Node {
120                                        width: px(200),
121                                        height: px(65),
122                                        border: UiRect::all(px(5)),
123                                        justify_content: JustifyContent::Center,
124                                        align_items: AlignItems::Center,
125                                        ..default()
126                                    },
127                                    BorderColor::all(Color::BLACK),
128                                    BackgroundColor(NORMAL_BUTTON),
129                                    TabIndex(i),
130                                    children![(
131                                        Text::new(format!("TabIndex {i}")),
132                                        TextFont {
133                                            font_size: 20.0,
134                                            ..default()
135                                        },
136                                        TextColor(Color::srgb(0.9, 0.9, 0.9)),
137                                    )],
138                                ))
139                                .observe(
140                                    |mut click: On<Pointer<Click>>,
141                                    mut focus: ResMut<InputFocus>| {
142                                        focus.0 = Some(click.entity);
143                                        click.propagate(false);
144                                    },
145                                );
146                        }
147                    });
148            }
149        });
150}