//! Demonstrates how to set up the directional navigation system to allow for navigation between widgets. //! //! Directional navigation is generally used to move between widgets in a user interface using arrow keys or gamepad input. //! When compared to tab navigation, directional navigation is generally more direct, and less aware of the structure of the UI. //! //! 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::{ DirectionalNavigation, DirectionalNavigationMap, DirectionalNavigationPlugin, }, InputDispatchPlugin, InputFocus, InputFocusVisible, }, math::{CompassOctant, FloatOrd}, picking::{ backend::HitData, pointer::{Location, PointerId}, }, platform::collections::{HashMap, HashSet}, prelude::*, render::camera::NormalizedRenderTarget, }; fn main() { App::new() // Input focus is not enabled by default, so we need to add the corresponding plugins .add_plugins(( DefaultPlugins, InputDispatchPlugin, DirectionalNavigationPlugin, )) // This resource is canonically used to track whether or not to render a focus indicator // It starts as false, but we set it to true here as we would like to see the focus indicator .insert_resource(InputFocusVisible(true)) // We've made a simple resource to keep track of the actions that are currently being pressed for this example .init_resource::() .add_systems(Startup, setup_ui) // Input is generally handled during PreUpdate // We're turning inputs into actions first, then using those actions to determine navigation .add_systems(PreUpdate, (process_inputs, navigate).chain()) .add_systems( Update, ( // We need to show which button is currently focused highlight_focused_element, // Pressing the "Interact" button while we have a focused element should simulate a click interact_with_focused_button, // We're doing a tiny animation when the button is interacted with, // so we need a timer and a polling mechanism to reset it reset_button_after_interaction, ), ) // This observer is added globally, so it will respond to *any* trigger of the correct type. // However, we're filtering in the observer's query to only respond to button presses .add_observer(universal_button_click_behavior) .run(); } const NORMAL_BUTTON: Srgba = bevy::color::palettes::tailwind::BLUE_400; const PRESSED_BUTTON: Srgba = bevy::color::palettes::tailwind::BLUE_500; const FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::BLUE_50; // This observer will be triggered whenever a button is pressed // 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( mut trigger: Trigger>, mut button_query: Query<(&mut BackgroundColor, &mut ResetTimer)>, ) { let button_entity = trigger.target(); if let Ok((mut color, mut reset_timer)) = button_query.get_mut(button_entity) { // This would be a great place to play a little sound effect too! color.0 = PRESSED_BUTTON.into(); reset_timer.0 = Timer::from_seconds(0.3, TimerMode::Once); // Picking events propagate up the hierarchy, // so we need to stop the propagation here now that we've handled it trigger.propagate(false); } } /// Resets a UI element to its default state when the timer has elapsed. #[derive(Component, Default, Deref, DerefMut)] struct ResetTimer(Timer); fn reset_button_after_interaction( time: Res