- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 4.2k
Add Automatic Directional Navigation Graph Generation #21668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…navigation system - Introduced example demonstrating automatic navigation with zero configuration. - Updated example to reference the new automatic navigation capabilities. - Enhanced to support automatic graph generation based on UI element positions. - Added component for automatic navigation graph maintenance.
| opening this up for review, now that CI has passed! @alice-i-cecile @viridia @fallible-algebra | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I think this is great.
I'm a little bit concerned about the overlap restriction. Imagine a UI in which you have a constellation of stars (like the magic sklill trees in skyrim) where the individual nodes of the tree are very small and the space between them large. In such a case, nodes will hardly ever overlap along an axis, but you'd still want to navigate between nodes where the line connecting the two nodes was at an angle close to the nav direction.
| /// | ||
| /// For example, when navigating East/West, nodes must have some vertical overlap. | ||
| /// A value of 0.0 means any overlap is acceptable, while 1.0 means perfect alignment is required. | ||
| pub min_alignment_factor: f32, | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an angle or a distance? What are the units?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great question - it's neither an angle nor distance, it's actually a ratio - it represents the fraction of overlap between 2 nodes on the perpendicular axis:
- For N/S navigation: it measures horizontal overlap
- For E/W navigation: it measures vertical overlap
- For diagonal directions(NE/NW/SE/SW): it's ignored (always considered 1.0)
And the calculation computes this as: overlap_ratio = actual_overlap / min(origin_size, candidate_size)
For example:
- Origin node: width 100px, centered at x=150 => spans [100, 200]
- Candidate node: width 100px, centered at x=180 => spans [130, 230]
- Overlap region: [130, 200] = 70px
- Overlap ratio: 70 / 100 = 0.7
If min_alignment_factor = 0.5, this node would be reachable (as 0.7 >= 0.5). However, if min_alignment_factor = 0.8, it would not be.
I could modify the name of this to be more descriptive - something like min_overlap_ratio? Or perhaps better improve the docs to reflect?
| 
 This is a great point! However, I think the default configuration would actually handle this scenario: 
 That said, I agree the overlap restriction could be problematic for extremely sparse layouts where nodes might not overlap at all on the perpendicular axis, but there are some mitigating factors in the design currently: 
 What we could do for now is improve documentation around this and clarify why/when overlap matters and suggest possible workarounds for this with extremely sparse UIs? | 
| How is the graph connectivity with this algorithm? Maybe there could be some pattern where we run the solver recursively with increasingly more lenient parameters until the graph is sufficiently dense? (This could be up to the user, but we should have a clean demonstration on how to do it.) PS: I would suggest looking at how Shift+Arrow navigation works in a browser as prior art. | 
| /// Whether to also consider `TabIndex` for navigation order hints. | ||
| /// Currently unused but reserved for future functionality. | ||
| pub respect_tab_order: bool, | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the behavior when entities with AutoDirectionalNavigation are located at the same position, but in different UI layers.
For example, separate UI entity trees that have different root GlobalZIndex, but they are top on each other. You can imagine two floating windows on top of each other, or dropdown on top of UI.
Does this algorithm handle each UI layer separately? Is it possible to configure it to recognize such layers (TabGroup, TabIndex ? ).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, the algorithm is z-index agnostic - it treats all entities with AutoDirectionalNavigation as a flat set based purely on XY position. So overlapping windows/layers would incorrectly connect to each other.
For proper layer handling, you'd need to:
- Query entities per-layer manually and call auto_generate_navigation_edges()separately for each layer, OR
- Use manual edges to explicitly define cross-layer navigation when desired
To avoid making this PR too large, I would add automatic layer support as a future enhancement. You could:
- Filter by TabGroup component, as suggested!
- Add layer_id field to AutoDirectionalNavigation
- Use parent entity hierarchy to detect separate UI trees
Would like to know if you think it's necessary for this PR to address this, or is a follow-up PR better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it can be done in future PR when bevy_ui with multiple layers is being used more widely.
Good to hear that currently there is workaround where auto_generate_navigation_edges can be called manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Thanks for the comments @IQuick143 and @PPakalns I'll try and address these tomorrow - it's late here for me now 😀 | 
| 
 With default config, the graph should be weakly connected in most layouts, but it's not guaranteed due to the directional cone filter + overlap requirements. I do like your progressive relaxation idea, however. We could add a helper function: pub fn ensure_connectivity(
    nav_map: &mut DirectionalNavigationMap,
    nodes: &[(Entity, Vec2, Vec2)],
) {
    // Try default config, then relax if disconnected
}Or perhaps better, is to just add a config parameter for the user to define: pub struct AutoNavigationConfig {
    // ... existing fields ...
    /// If true, ensures all nodes are reachable by relaxing constraints
    /// for nodes that would otherwise be isolated
    pub ensure_connectivity: bool,
}Then we can demonstrate this usage in the  I think either of these options give the user control of how to manage the connectivity issue you've described, but remains simple enough so as to not pollute with too many options. What do you think of this? Re: browser shift+arrow - based on my research it appears to use projection-based selection to guarantee connectivity. It might be best to add this as an alternative mode in the future, and support other algorithms? Something like: pub enum NavigationAlgorithm {
      OverlapBased,  // Current design
      ProjectionBased, // Like browser Shift+Arrow?
} | 
| It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note. Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes. | 
| // Extract center position from transform | ||
| let (_scale, _rotation, translation) = transform.to_scale_angle_translation(); | ||
| let size = computed.size(); | ||
| (entity, translation, size) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May I suggest using a struct with named fields here?
Objective
Resolves #21661
Adds automatic directional navigation graph generation based on UI node positions and sizes, eliminating the need for tedious manual graph construction in dynamic UIs.
Solution
Implements a spatial navigation algorithm that automatically computes the nearest neighbor in each compass direction for UI elements, while respecting any manually-defined edges.
Features
AutoNavigationConfigresource allows tuning alignment requirements, distance limits, and preference weightingAutoDirectionalNavigationcomponent added to use, and therefore not a breaking changeVec2position/size data, not justbevy_uiImplementation
New Components & Resources (
bevy_input_focus/src/directional_navigation.rs):AutoDirectionalNavigation- Marker component to enable auto-navigationAutoNavigationConfig- Configuration resource with settings:min_alignment_factor: Minimum perpendicular overlap (0.0-1.0) required for cardinal directionsmax_search_distance: Optional distance limit for connectionsprefer_aligned: Whether to strongly prefer well-aligned nodesCore Algorithm:
For each node and each direction:
Scoring Formula:
This makes misaligned nodes significantly less attractive while still considering distance.
Usage
Before (manual):
After:
Testing
auto_directional_navigationdirectional_navigationShowcase
New Example:
auto_directional_navigationDemonstrates automatic navigation with irregularly-positioned buttons. Unlike a regular grid, these buttons are scattered, but auto-navigation figures out the correct connections - also shows currently focused button, and the last "input" pressed to show the logical flow of navigating:
Recording.2025-10-27.142557.mp4
Key differences from manual
directional_navigationexample:add_edges()oradd_looping_edges()callsMigration Guide
No breaking changes - this is a purely additive feature.
To adopt automatic navigation:
AutoDirectionalNavigationcomponent to focusable entitiesAutoNavigationConfigresource