Crate bevy_ui_navigation
source · [−]Expand description
Bevy UI navigation
A generic UI navigation algorithm meant to be adaptable to any UI library, but currently limiting itself to targeting the Bevy engine default UI library.
[dependencies]
bevy-ui-navigation = "0.11.1"The in-depth design specification is available here.
Examples
Check out the examples directory for bevy examples.
Ultimate navigation example demo

Cargo Features
This crate exposes the bevy-ui feature. It is enabled by default. Toggling
off this feature let you compile this crate without requiring the bevy render
feature. But you won’t be able to use FocusableButtonBundle, and you’ll have
to use generic_default_mouse_input for mouse input and define special spacial
components to get it working.
Usage
See this example for a quick start guide.
Simple case
You just have a collection of buttons displayed on screen and you want the
player to be able to select between them with their controller? Simply use the
bevy_ui_navigation::components::FocusableButtonBundle instead of
ButtonBundle. The navigation system is capable of switching focus based on
the 2D position of each focusable element.
This won’t work out of the box though, you must wire the UI to the control
scheme, by sending requests to the NavRequest event queue. We do not provide
out of the box a way to do this, but we provide default input handling systems.
Try this:
use bevy::prelude::*;
use bevy_ui_navigation::systems::{default_gamepad_input, default_keyboard_input, InputMapping};
use bevy_ui_navigation::NavigationPlugin;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(NavigationPlugin)
.init_resource::<InputMapping>()
.add_system(default_keyboard_input)
.add_system(default_gamepad_input)
.run();
}The default button mapping may not be what you want, or you may want to change it
in-game (for example when the user is in an input mapping menu) The controls
are modifiable with the
InputMapping resource. Check out the doc for it for
more details.
Check the examples directory for more example code.
To respond to relevant user input, for example when the player pressed the
“Action” button when focusing start_game_button, you should read the
NavEvent event queue:
use bevy::prelude::*;
use bevy_ui_navigation::{NavEvent, NavRequest};
struct Gameui {
start_game_button: Entity,
}
fn handle_nav_events(mut events: EventReader<NavEvent>, game: Res<Gameui>) {
use bevy_ui_navigation::{NavEvent::NoChanges, NavRequest::Action};
for event in events.iter() {
match event {
NoChanges { from, request: Action } if *from.first() == game.start_game_button => {
// Start the game on "A" or "ENTER" button press
}
_ => {}
}
}
}The focus navigation works across the whole UI tree, regardless of how or where you’ve put your focusable entities. You just move in the direction you want to go, and you get there.
In truth,
FocusableButtonBundle is just a ButtonBundle with an extra
Focusable component field.
Any Entity can be converted into a focusable entity by adding the Focusable
component to it. To do so, just:
fn system(mut cmds: Commands, my_entity: Entity) {
cmds.entity(my_entity).insert(Focusable::default());
}That’s it! Now my_entity is part of the navigation tree. The player can
select it with their controller the same way as any other Focusable element.
You probably want to render the focused button differently than other buttons,
this can be done with the Changed<Focusable> query parameter as follow:
use bevy::prelude::*;
use bevy_ui_navigation::Focusable;
fn button_system(
mut focusables: Query<(&Focusable, &mut UiColor), Changed<Focusable>>,
) {
for (focus_state, mut color) in focusables.iter_mut() {
let new_color = if focus_state.is_focused() {
Color::RED
} else {
Color::BLUE
};
*color = new_color.into();
}
}More complex use cases
With NavMenus
Suppose you have a more complex game with menus sub-menus and sub-sub-menus etc. For example, in your everyday 2021 AAA game, to change the antialiasing you would go through a few menus:
game menu → options menu → graphics menu → custom graphics menu → AAIn this case, you need to be capable of specifying which button in the previous menu leads to the next menu (for example, you would press the “Options” button in the game menu to access the options menu).
For that, you need to use the
NavMenu component.
- First you need a “root”
NavMenu. This is a limit of the navigation algorithm (which may be fixed in the future) - You also need the
Entityvalue of your “Options” button - You then add a
NavMenu::reachable_from(options_button)to theNodeBundlecontaining all the options menuFocusableentities. This may look like this:
use bevy::prelude::*;
use bevy_ui_navigation::NavMenu;
use bevy_ui_navigation::components::FocusableButtonBundle;
fn spawn_menu(mut cmds: Commands) {
let menu = NodeBundle {
style: Style { flex_direction: FlexDirection::Column, ..Default::default()},
..Default::default()
};
let button = FocusableButtonBundle::from(ButtonBundle {
color: Color::rgb(1.0, 0.3, 1.0).into(),
..Default::default()
});
let mut spawn = |bundle: &FocusableButtonBundle| {
cmds.spawn_bundle(bundle.clone()).id()
};
let options = spawn(&button);
let graphics_option = spawn(&button);
let audio_options = spawn(&button);
let input_options = spawn(&button);
let game = spawn(&button);
let quit = spawn(&button);
let load = spawn(&button);
// Spawn the game menu, with a `NavMenu`
let game_menu = cmds
.spawn_bundle(menu.clone())
// vvvvvvvvvvvvv
.insert(NavMenu::root())
.push_children(&[options, game, quit, load])
.id();
// Spawn the options menu
let options_menu = cmds
.spawn_bundle(menu)
// !!vvvvvvvvvvvvvvvvvvvvvvv!!
.insert(NavMenu::reachable_from(options))
.push_children(&[graphics_option, audio_options, input_options])
.id();
}With this, your game menu will be isolated from your options menu, you can only
access it by sending the NavRequest::Action when options_button is focused,
or by sending a
NavRequest::FocusOn(input_options_button).
Specifically, navigation between Focusable entities will be constrained to
other Focusable that are children of the same NavMenu. It creates a
self-contained menu.
NavRequest::FocusOn
You can’t directly manipulate which entity is focused, because we need to keep
track of a lot of thing on the backend to make the navigation work as expected.
But you can set the focused element to any arbitrary entity with the
NavRequest::FocusOn request.
NavMenu settings
A NavMenu doesn’t only define menu-to-menu navigation, but it also gives you
finner-grained control on how navigation is handled within a menu.
NavMenu::{cycle, closed} controls whether or not going left from the
leftmost element goes to the rightmost and vis-versa.
NavMenu::scope
sets the menu as a sort of global control menu. It catches NavRequest::ScopeMove
requests even when the focused entity is in another sub-menu reachable from this
menu. This behaves like you would expect a tabbed menu to behave. See the
“ultimate” menu navigation
example
for a demonstration.
Marking
If you need to know from which menu a
NavEvent::FocusChanged originated, you may want to use the marker module.
A usage demo is available in the marking.rs example
Locking
If you need to supress the navigation algorithm temporarily, you can declare a
Focusable as
Focusable::lock.
This is useful for example if you want to implement custom widget with their
own controls, or if you want to disable menu navigation while in game. To
resume the navigation system, you’ll need to send a
NavRequest::Free.
Changelog
0.8.2: Fix offsetting of mouse focus withUiCameras with a transform set to anything else than zero.0.9.0: AddFocusable::cancel(see documentation for details); Add warning message rather than do dumb things when there is more than a singleNavRequestper frame0.9.1: Fix #8, Panic on diagonal gamepad input0.10.0: Add thebevy-uifeature, technically this includes breaking changes, but it is very unlikely you need to change your code to get it working- Breaking: if you were manually calling
default_mouse_input, it now has additional parameters - Breaking:
ui_focusable_atandNodePosQuerynow have type parameters
- Breaking: if you were manually calling
0.11.0: Add theFocusable::lockfeature. A focusable now can be declared as “lock” and block the ui navigation systems until the user sends aNavRequest::Free. See thelocking.rsexample for illustration.- Breaking: New enum variants on
NavRequestandNavEvent
- Breaking: New enum variants on
0.11.1: Add themarkermodule, enabling propagation of user-specified components toFocusablechildren of aNavMenu.
License
Copyright © 2021 Nicola Papale
This software is licensed under either MIT or Apache 2.0 at your leisure. See LICENSE file for details.
Modules
Focusable components and bundles to ease navigable UI declaration
Automatic menu marker component propagation.
System for the navigation tree and default input systems to get started
Structs
The navigation system’s lock.
A menu that isolate children Focusables from other focusables and
specify navigation method within itself.
The navigation plugin
Non empty vector, ensure non empty by construction.
Inherits Vec’s methods through Deref trait, not implement DerefMut.
Overridden these methods:
Enums
2d direction to move in normal menus
State of a Focusable.
Events emitted by the navigation system.
Requests to send to the navigation system to update focus
Direction for movement in NavMenu::scope menus.