Skip to content

Commit 145f5f4

Browse files
Add a simple directional UI navigation example (#17224)
# Objective Gamepad / directional navigation needs an example, for both teaching and testing purposes. ## Solution - Add a simple grid-based example. - Fix an intermittent panic caused by a race condition with bevy_a11y - Clean up small issues noticed in bevy_input_focus ![image](https://github.com/user-attachments/assets/3a924255-0cd6-44a5-9bb7-b2c400a22d7e) ## To do: this PR - [x] figure out why "enter" isn't doing anything - [x] change button color on interaction rather than printing - [x] add on-screen directions - [x] move to an asymmetric grid to catch bugs - [x] ~~fix colors not resetting on button press~~ lol this is mostly just a problem with hacking `Interaction` for this - [x] swap to using observers + bubbling, rather than `Interaction` ## To do: future work - when I increase the button size, such that there is no line break, the text on the buttons is no longer centered :( EDIT: this is #16783 - add gamepad stick navigation - add tools to find the nearest populated quadrant to make diagonal inputs work - add a `add_edges` method to `DirectionalNavigationMap` - add a `add_grid` method to `DirectionalNavigationMap` - make the example's layout more complex and realistic - add tools to automatically generate this list - add button shake on failed navigation rather than printing an error - make Pressed events easier to mock: default fields, PointerId::Focus ## Testing `cargo run --example directional_navigation` --------- Co-authored-by: Rob Parrett <[email protected]>
1 parent 0a9740c commit 145f5f4

File tree

5 files changed

+444
-5
lines changed

5 files changed

+444
-5
lines changed

Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4014,6 +4014,17 @@ doc-scrape-examples = true
40144014

40154015
[package.metadata.example.tab_navigation]
40164016
name = "Tab Navigation"
4017-
description = "Demonstration of Tab Navigation"
4017+
description = "Demonstration of Tab Navigation between UI elements"
4018+
category = "UI (User Interface)"
4019+
wasm = true
4020+
4021+
[[example]]
4022+
name = "directional_navigation"
4023+
path = "examples/ui/directional_navigation.rs"
4024+
doc-scrape-examples = true
4025+
4026+
[package.metadata.example.directional_navigation]
4027+
name = "Directional Navigation"
4028+
description = "Demonstration of Directional Navigation between UI elements"
40184029
category = "UI (User Interface)"
40194030
wasm = true

crates/bevy_input_focus/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ impl InputFocus {
114114
/// By contrast, a console-style UI intended to be navigated with a gamepad may always have the focus indicator visible.
115115
///
116116
/// To easily access information about whether focus indicators should be shown for a given entity, use the [`IsFocused`] trait.
117-
#[derive(Clone, Debug, Resource)]
117+
///
118+
/// By default, this resource is set to `false`.
119+
#[derive(Clone, Debug, Resource, Default)]
118120
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Resource))]
119121
pub struct InputFocusVisible(pub bool);
120122

@@ -174,8 +176,8 @@ pub struct InputDispatchPlugin;
174176
impl Plugin for InputDispatchPlugin {
175177
fn build(&self, app: &mut App) {
176178
app.add_systems(Startup, set_initial_focus)
177-
.insert_resource(InputFocus(None))
178-
.insert_resource(InputFocusVisible(false))
179+
.init_resource::<InputFocus>()
180+
.init_resource::<InputFocusVisible>()
179181
.add_systems(
180182
PreUpdate,
181183
(

crates/bevy_winit/src/accessibility.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ fn update_accessibility_nodes(
198198
return;
199199
};
200200
if focus.is_changed() || !nodes.is_empty() {
201+
// Don't panic if the focused entity does not currently exist
202+
// It's probably waiting to be spawned
203+
if let Some(focused_entity) = focus.0 {
204+
if !node_entities.contains(focused_entity) {
205+
return;
206+
}
207+
}
208+
201209
adapter.update_if_active(|| {
202210
update_adapter(
203211
nodes,

examples/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ Example | Description
512512
[Box Shadow](../examples/ui/box_shadow.rs) | Demonstrates how to create a node with a shadow
513513
[Button](../examples/ui/button.rs) | Illustrates creating and updating a button
514514
[CSS Grid](../examples/ui/grid.rs) | An example for CSS Grid layout
515+
[Directional Navigation](../examples/ui/directional_navigation.rs) | Demonstration of Directional Navigation between UI elements
515516
[Display and Visibility](../examples/ui/display_and_visibility.rs) | Demonstrates how Display and Visibility work in the UI.
516517
[Flex Layout](../examples/ui/flex_layout.rs) | Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text
517518
[Font Atlas Debug](../examples/ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
@@ -523,7 +524,7 @@ Example | Description
523524
[Render UI to Texture](../examples/ui/render_ui_to_texture.rs) | An example of rendering UI as a part of a 3D world
524525
[Scroll](../examples/ui/scroll.rs) | Demonstrates scrolling UI containers
525526
[Size Constraints](../examples/ui/size_constraints.rs) | Demonstrates how the to use the size constraints to control the size of a UI node.
526-
[Tab Navigation](../examples/ui/tab_navigation.rs) | Demonstration of Tab Navigation
527+
[Tab Navigation](../examples/ui/tab_navigation.rs) | Demonstration of Tab Navigation between UI elements
527528
[Text](../examples/ui/text.rs) | Illustrates creating and updating text
528529
[Text Debug](../examples/ui/text_debug.rs) | An example for debugging text layout
529530
[Text Wrap Debug](../examples/ui/text_wrap_debug.rs) | Demonstrates text wrapping

0 commit comments

Comments
 (0)