Skip to content

Commit

Permalink
Add a simple directional UI navigation example (#17224)
Browse files Browse the repository at this point in the history
# 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 <robparrett@gmail.com>
  • Loading branch information
alice-i-cecile and rparrett authored Jan 9, 2025
1 parent 0a9740c commit 145f5f4
Show file tree
Hide file tree
Showing 5 changed files with 444 additions and 5 deletions.
13 changes: 12 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4014,6 +4014,17 @@ doc-scrape-examples = true

[package.metadata.example.tab_navigation]
name = "Tab Navigation"
description = "Demonstration of Tab Navigation"
description = "Demonstration of Tab Navigation between UI elements"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "directional_navigation"
path = "examples/ui/directional_navigation.rs"
doc-scrape-examples = true

[package.metadata.example.directional_navigation]
name = "Directional Navigation"
description = "Demonstration of Directional Navigation between UI elements"
category = "UI (User Interface)"
wasm = true
8 changes: 5 additions & 3 deletions crates/bevy_input_focus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ impl InputFocus {
/// By contrast, a console-style UI intended to be navigated with a gamepad may always have the focus indicator visible.
///
/// To easily access information about whether focus indicators should be shown for a given entity, use the [`IsFocused`] trait.
#[derive(Clone, Debug, Resource)]
///
/// By default, this resource is set to `false`.
#[derive(Clone, Debug, Resource, Default)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Resource))]
pub struct InputFocusVisible(pub bool);

Expand Down Expand Up @@ -174,8 +176,8 @@ pub struct InputDispatchPlugin;
impl Plugin for InputDispatchPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, set_initial_focus)
.insert_resource(InputFocus(None))
.insert_resource(InputFocusVisible(false))
.init_resource::<InputFocus>()
.init_resource::<InputFocusVisible>()
.add_systems(
PreUpdate,
(
Expand Down
8 changes: 8 additions & 0 deletions crates/bevy_winit/src/accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@ fn update_accessibility_nodes(
return;
};
if focus.is_changed() || !nodes.is_empty() {
// Don't panic if the focused entity does not currently exist
// It's probably waiting to be spawned
if let Some(focused_entity) = focus.0 {
if !node_entities.contains(focused_entity) {
return;
}
}

adapter.update_if_active(|| {
update_adapter(
nodes,
Expand Down
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ Example | Description
[Box Shadow](../examples/ui/box_shadow.rs) | Demonstrates how to create a node with a shadow
[Button](../examples/ui/button.rs) | Illustrates creating and updating a button
[CSS Grid](../examples/ui/grid.rs) | An example for CSS Grid layout
[Directional Navigation](../examples/ui/directional_navigation.rs) | Demonstration of Directional Navigation between UI elements
[Display and Visibility](../examples/ui/display_and_visibility.rs) | Demonstrates how Display and Visibility work in the UI.
[Flex Layout](../examples/ui/flex_layout.rs) | Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text
[Font Atlas Debug](../examples/ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
Expand All @@ -523,7 +524,7 @@ Example | Description
[Render UI to Texture](../examples/ui/render_ui_to_texture.rs) | An example of rendering UI as a part of a 3D world
[Scroll](../examples/ui/scroll.rs) | Demonstrates scrolling UI containers
[Size Constraints](../examples/ui/size_constraints.rs) | Demonstrates how the to use the size constraints to control the size of a UI node.
[Tab Navigation](../examples/ui/tab_navigation.rs) | Demonstration of Tab Navigation
[Tab Navigation](../examples/ui/tab_navigation.rs) | Demonstration of Tab Navigation between UI elements
[Text](../examples/ui/text.rs) | Illustrates creating and updating text
[Text Debug](../examples/ui/text_debug.rs) | An example for debugging text layout
[Text Wrap Debug](../examples/ui/text_wrap_debug.rs) | Demonstrates text wrapping
Expand Down
Loading

0 comments on commit 145f5f4

Please sign in to comment.