//! Demonstrates automatic directional navigation. //! //! This shows how to use automatic navigation by simply adding the [`AutoDirectionalNavigation`] //! component to UI elements. Navigation is automatically calculated based on screen positions. //! //! This is especially useful for: //! - Dynamic UIs where elements may be added, removed, or repositioned //! - Irregular layouts that don't fit a simple grid pattern //! - Prototyping where you want navigation without tedious manual setup //! //! The automatic system finds the nearest neighbor in each compass direction for every node, //! completely eliminating the need to manually specify navigation relationships. //! //! For an example that demonstrates automatic directional navigation with manual overrides, //! refer to the `directional_navigation_overrides` example. use core::time::Duration; use bevy::{ camera::NormalizedRenderTarget, input_focus::{ directional_navigation::{AutoNavigationConfig, DirectionalNavigationPlugin}, tab_navigation::TabIndex, AutoFocus, InputFocus, InputFocusVisible, }, math::{CompassOctant, Dir2, Rot2}, picking::{ backend::HitData, pointer::{Location, PointerId}, }, platform::collections::HashSet, prelude::*, ui::auto_directional_navigation::{AutoDirectionalNavigation, AutoDirectionalNavigator}, ui_widgets::Button, }; fn main() { App::new() .add_plugins((DefaultPlugins, 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)) // Configure auto-navigation behavior .insert_resource(AutoNavigationConfig { // Require at least 10% overlap in perpendicular axis for cardinal directions min_alignment_factor: 0.1, // Don't connect nodes more than 500 pixels apart between their closest edges max_search_distance: Some(500.0), // Prefer nodes that are well-aligned prefer_aligned: true, }) .init_resource::() .add_systems(Startup, (setup_scattered_ui, init_focus).chain()) // No manual system needed - just add AutoDirectionalNavigation to entities. // Input is generally handled during PreUpdate .add_systems(PreUpdate, (process_inputs, navigate).chain()) .add_systems( Update, ( highlight_focused_element, interact_with_focused_button, reset_button_after_interaction, update_focus_display .run_if(|input_focus: Res| input_focus.is_changed()), update_key_display, ), ) .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; /// Marker component for the text that displays the currently focused button #[derive(Component, Clone, Default)] struct FocusDisplay; /// Marker component for the text that displays the last key pressed #[derive(Component, Clone, Default)] struct KeyDisplay; // Observer for button clicks. Clicking on a button is considered navigation to that button. // This is also triggered when `interact_with_focused_button` simulates a button click with a // manually sent pointer click event fn universal_button_click_behavior( mut click: On, mut button_query: Query<(&mut BackgroundColor, &mut ResetTimer)>, mut focus_visible: ResMut, ) { let button_entity = click.entity; if let Ok((mut color, mut reset_timer)) = button_query.get_mut(button_entity) { color.0 = PRESSED_BUTTON.into(); reset_timer.0 = Timer::from_seconds(0.3, TimerMode::Once); click.propagate(false); // The focus should be visible because a button was clicked. // It may have been set to false by the `PointerFocusPlugin` focus_visible.0 = true; } } #[derive(Clone, Component, Default, Deref, DerefMut)] struct ResetTimer(Timer); fn reset_button_after_interaction( time: Res