Small improvements for directional navigation (#17395)

# Objective

While working on more complex directional navigation work, I noticed a
few small things.

## Solution

Rather than stick them in a bigger PR, split them out now.

- Include more useful information when responding to
`DirectionalNavigationError`.
- Use the less controversial `Click` events (rather than `Pressed`) in
the example
- Implement add_looping_edges in terms of `add_edges`. Thanks @rparrett
for the idea.

## Testing

Ran the `directional_navigation` example and things still work.
This commit is contained in:
Alice Cecile 2025-01-16 20:15:39 -05:00 committed by GitHub
parent b693362b0c
commit 3737f86d84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 25 additions and 15 deletions

View File

@ -186,10 +186,11 @@ impl DirectionalNavigationMap {
///
/// This is useful for creating a circular navigation path between a set of entities, such as a menu.
pub fn add_looping_edges(&mut self, entities: &[Entity], direction: CompassOctant) {
for i in 0..entities.len() {
let a = entities[i];
let b = entities[(i + 1) % entities.len()];
self.add_symmetrical_edge(a, b, direction);
self.add_edges(entities, direction);
if let Some((first_entity, rest)) = entities.split_first() {
if let Some(last_entity) = rest.last() {
self.add_symmetrical_edge(*last_entity, *first_entity, direction);
}
}
}
@ -227,14 +228,17 @@ impl DirectionalNavigation<'_> {
/// If the result was `Ok`, the [`InputFocus`] resource is updated to the new focus as part of this method call.
pub fn navigate(
&mut self,
octant: CompassOctant,
direction: CompassOctant,
) -> Result<Entity, DirectionalNavigationError> {
if let Some(current_focus) = self.focus.0 {
if let Some(new_focus) = self.map.get_neighbor(current_focus, octant) {
if let Some(new_focus) = self.map.get_neighbor(current_focus, direction) {
self.focus.set(new_focus);
Ok(new_focus)
} else {
Err(DirectionalNavigationError::NoNeighborInDirection)
Err(DirectionalNavigationError::NoNeighborInDirection {
current_focus,
direction,
})
}
} else {
Err(DirectionalNavigationError::NoFocus)
@ -249,8 +253,13 @@ pub enum DirectionalNavigationError {
#[error("No focusable entity is currently set.")]
NoFocus,
/// No neighbor in the requested direction.
#[error("No neighbor in the requested direction.")]
NoNeighborInDirection,
#[error("No neighbor from {current_focus} in the {direction:?} direction.")]
NoNeighborInDirection {
/// The entity that was the focus when the error occurred.
current_focus: Entity,
/// The direction in which the navigation was attempted.
direction: CompassOctant,
},
}
#[cfg(test)]

View File

@ -5,6 +5,8 @@
//!
//! In this example, we will set up a simple UI with a grid of buttons that can be navigated using the arrow keys or gamepad input.
use std::time::Duration;
use bevy::{
input_focus::{
directional_navigation::{
@ -65,9 +67,7 @@ const FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::BLUE_50;
// In a real project, each button would also have its own unique behavior,
// to capture the actual intent of the user
fn universal_button_click_behavior(
// We're using an on-mouse-down trigger to improve responsiveness;
// Clicked is better when you want roll-off cancellation
mut trigger: Trigger<Pointer<Pressed>>,
mut trigger: Trigger<Pointer<Click>>,
mut button_query: Query<(&mut BackgroundColor, &mut ResetTimer)>,
) {
let button_entity = trigger.target();
@ -368,7 +368,7 @@ fn highlight_focused_element(
}
}
// By sending a Pointer<Pressed> trigger rather than directly handling button-like interactions,
// By sending a Pointer<Click> trigger rather than directly handling button-like interactions,
// we can unify our handling of pointer and keyboard/gamepad interactions
fn interact_with_focused_button(
action_state: Res<ActionState>,
@ -381,7 +381,7 @@ fn interact_with_focused_button(
{
if let Some(focused_entity) = input_focus.0 {
commands.trigger_targets(
Pointer::<Pressed> {
Pointer::<Click> {
target: focused_entity,
// We're pretending that we're a mouse
pointer_id: PointerId::Mouse,
@ -395,7 +395,7 @@ fn interact_with_focused_button(
),
position: Vec2::ZERO,
},
event: Pressed {
event: Click {
button: PointerButton::Primary,
// This field isn't used, so we're just setting it to a placeholder value
hit: HitData {
@ -404,6 +404,7 @@ fn interact_with_focused_button(
position: None,
normal: None,
},
duration: Duration::from_secs_f32(0.1),
},
},
focused_entity,