Path: blob/main/examples/ui/navigation/directional_navigation_overrides.rs
9434 views
//! Demonstrates automatic directional navigation with manual navigation overrides.1//!2//! This example shows how to leverage both automatic navigation and manual overrides to create3//! a desired user navigation experience without much boilerplate code. In this example, there are4//! multiple pages of UI Buttons that depict different scenarios in which both automatic and manual5//! navigation are leveraged to produce a desired navigation experience.6//!7//! Manual overrides can be used to define navigation in any situation where automatic8//! navigation fails to create an edge due to lack of proximity. For example, when creating9//! navigation that loops around to an opposite side, manual overrides should be used to define10//! this behavior. If one input is too far away from the others and `AutoNavigationConfig`11//! cannot be tweaked, manual overrides can connect that input to the others. Manual navigation12//! can also be used to override any undesired automatic navigation.13//!14//! The [`AutoDirectionalNavigation`] component is used to create basic, intuitive navigation to UI15//! elements within a page. Manual navigation edges are added to the [`DirectionalNavigationMap`]16//! to create special navigation rules. The [`AutoDirectionalNavigator`] system parameter navigates17//! using manual navigation rules/overrides first and automatic navigation second.1819use core::time::Duration;2021use bevy::{22camera::NormalizedRenderTarget,23input_focus::{24directional_navigation::{25AutoNavigationConfig, DirectionalNavigationMap, DirectionalNavigationPlugin,26},27InputDispatchPlugin, InputFocus, InputFocusVisible,28},29math::{CompassOctant, Dir2},30picking::{31backend::HitData,32pointer::{Location, PointerId},33},34platform::collections::HashSet,35prelude::*,36ui::auto_directional_navigation::{AutoDirectionalNavigation, AutoDirectionalNavigator},37};3839fn main() {40App::new()41// Input focus is not enabled by default, so we need to add the corresponding plugins42// The navigation system's resources are initialized by the DirectionalNavigationPlugin.43.add_plugins((44DefaultPlugins,45InputDispatchPlugin,46DirectionalNavigationPlugin,47))48// This resource is canonically used to track whether or not to render a focus indicator49// It starts as false, but we set it to true here as we would like to see the focus indicator50.insert_resource(InputFocusVisible(true))51// Configure auto-navigation behavior52.insert_resource(AutoNavigationConfig {53// Require at least 10% overlap in perpendicular axis for cardinal directions54min_alignment_factor: 0.1,55// Don't connect nodes more than 200 pixels apart between their closest edges56max_search_distance: Some(200.0),57// Prefer nodes that are well-aligned58prefer_aligned: true,59})60.init_resource::<ActionState>()61// For automatic navigation, UI entities will have the component `AutoDirectionalNavigation`62// and will be automatically connected by the navigation system.63// We will also add some new edges that the automatic navigation system64// cannot create by itself by inserting them into `DirectionalNavigationMap`65.add_systems(Startup, setup_paged_ui)66// Input is generally handled during PreUpdate67.add_systems(PreUpdate, (process_inputs, navigate).chain())68.add_systems(69Update,70(71highlight_focused_element,72interact_with_focused_button,73reset_button_after_interaction,74update_focus_display75.run_if(|input_focus: Res<InputFocus>| input_focus.is_changed()),76update_key_display,77),78)79.add_observer(universal_button_click_behavior)80.run();81}8283const PAGE_1_NORMAL_BUTTON: Srgba = bevy::color::palettes::tailwind::BLUE_400;84const PAGE_1_PRESSED_BUTTON: Srgba = bevy::color::palettes::tailwind::BLUE_500;85const PAGE_1_FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::BLUE_50;8687const PAGE_2_NORMAL_BUTTON: Srgba = bevy::color::palettes::tailwind::RED_400;88const PAGE_2_PRESSED_BUTTON: Srgba = bevy::color::palettes::tailwind::RED_500;89const PAGE_2_FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::RED_50;9091const PAGE_3_NORMAL_BUTTON: Srgba = bevy::color::palettes::tailwind::GREEN_400;92const PAGE_3_PRESSED_BUTTON: Srgba = bevy::color::palettes::tailwind::GREEN_500;93const PAGE_3_FOCUSED_BORDER: Srgba = bevy::color::palettes::tailwind::GREEN_50;9495const NORMAL_BUTTON_COLORS: [Srgba; 3] = [96PAGE_1_NORMAL_BUTTON,97PAGE_2_NORMAL_BUTTON,98PAGE_3_NORMAL_BUTTON,99];100const PRESSED_BUTTON_COLORS: [Srgba; 3] = [101PAGE_1_PRESSED_BUTTON,102PAGE_2_PRESSED_BUTTON,103PAGE_3_PRESSED_BUTTON,104];105const FOCUSED_BORDER_COLORS: [Srgba; 3] = [106PAGE_1_FOCUSED_BORDER,107PAGE_2_FOCUSED_BORDER,108PAGE_3_FOCUSED_BORDER,109];110111/// Marker component for the text that displays the currently focused button112#[derive(Component)]113struct FocusDisplay;114115/// Marker component for the text that displays the last key pressed116#[derive(Component)]117struct KeyDisplay;118119/// Component that stores which page a button is on120#[derive(Component)]121struct Page(usize);122123// Observer for button clicks124fn universal_button_click_behavior(125mut click: On<Pointer<Click>>,126mut button_query: Query<(&mut BackgroundColor, &Page, &mut ResetTimer)>,127) {128let button_entity = click.entity;129if let Ok((mut color, page, mut reset_timer)) = button_query.get_mut(button_entity) {130color.0 = PRESSED_BUTTON_COLORS[page.0].into();131reset_timer.0 = Timer::from_seconds(0.3, TimerMode::Once);132click.propagate(false);133}134}135136#[derive(Component, Default, Deref, DerefMut)]137struct ResetTimer(Timer);138139fn reset_button_after_interaction(140time: Res<Time>,141mut query: Query<(&mut ResetTimer, &mut BackgroundColor, &Page)>,142) {143for (mut reset_timer, mut color, page) in query.iter_mut() {144reset_timer.tick(time.delta());145if reset_timer.just_finished() {146color.0 = NORMAL_BUTTON_COLORS[page.0].into();147}148}149}150151/// Spawn pages of buttons to demonstrate automatic and manual navigation.152///153/// This function creates three pages of buttons. All buttons have automatic navigation154/// enabled by having the [`AutoDirectionalNavigation`] component.155/// Manual navigation is specified with the [`DirectionalNavigationMap`].156/// Page 1 has a simple grid of buttons where transitions between rows is defined using157/// the [`DirectionalNavigationMap`].158/// Page 2 has a cluster of buttons to the top left and a lonely button on the bottom right.159/// Navigation between the cluster and the lonely button is defined using the160/// [`DirectionalNavigationMap`].161/// Page 3 has the same simple grid of buttons as page 1, but automatic navigation has been162/// overridden in the vertical direction with the [`DirectionalNavigationMap`].163fn setup_paged_ui(164mut commands: Commands,165mut manual_directional_nav_map: ResMut<DirectionalNavigationMap>,166mut input_focus: ResMut<InputFocus>,167) {168commands.spawn(Camera2d);169170// Create a full-screen background node171let root_node = commands172.spawn(Node {173width: percent(100),174height: percent(100),175..default()176})177.id();178179// Instructions180let instructions = commands181.spawn((182Text::new(183"Directional Navigation Overrides Demo\n\n\184Use arrow keys or D-pad to navigate.\n\185Press Enter or A button to interact.\n\n\186Navigation on each page is a combination of \187both automatic and manual navigation.",188),189Node {190position_type: PositionType::Absolute,191left: px(20),192top: px(20),193width: px(280),194padding: UiRect::all(px(12)),195border_radius: BorderRadius::all(px(8)),196..default()197},198BackgroundColor(Color::srgba(0.1, 0.1, 0.1, 0.8)),199))200.id();201commands.entity(root_node).add_children(&[instructions]);202203// Focus display - shows which button is currently focused204commands.spawn((205Text::new("Focused: None"),206FocusDisplay,207Node {208position_type: PositionType::Absolute,209left: px(20),210bottom: px(80),211width: px(280),212padding: UiRect::all(px(12)),213border_radius: BorderRadius::all(px(8)),214..default()215},216BackgroundColor(Color::srgba(0.1, 0.5, 0.1, 0.8)),217TextFont {218font_size: FontSize::Px(20.0),219..default()220},221));222223// Key display - shows the last key pressed224commands.spawn((225Text::new("Last Key: None"),226KeyDisplay,227Node {228position_type: PositionType::Absolute,229left: px(20),230bottom: px(20),231width: px(280),232padding: UiRect::all(px(12)),233border_radius: BorderRadius::all(px(8)),234..default()235},236BackgroundColor(Color::srgba(0.5, 0.1, 0.5, 0.8)),237TextFont {238font_size: FontSize::Px(20.0),239..default()240},241));242243// Setup the pages with buttons and helper text244let mut pages_entities = [245Vec::with_capacity(12),246Vec::with_capacity(12),247Vec::with_capacity(12),248];249let mut text_entities = Vec::with_capacity(10);250for (page_num, page_button_entities) in pages_entities.iter_mut().enumerate() {251if page_num == 1 {252// the second page253setup_buttons_for_triangle_page(254&mut commands,255page_num,256(page_button_entities, &mut text_entities),257);258} else {259// the first and third pages are regular grids260setup_buttons_for_grid_page(261&mut commands,262page_num,263(page_button_entities, &mut text_entities),264);265}266267// Only the first page is visible at setup.268let visibility = if page_num == 0 {269Visibility::Visible270} else {271Visibility::Hidden272};273let page = commands274.spawn((275Node {276width: percent(100),277height: percent(100),278..default()279},280visibility,281))282.id();283284commands285.entity(page)286.add_children(page_button_entities)287.add_children(&text_entities);288289text_entities.clear();290}291292// For Pages 1 and 3, add manual edges within the grid page for navigation between rows.293let entity_pairs = [294// the end of the first row should connect to the beginning of the second295((0, 2), (1, 0)),296// the end of the second row should connect to the beginning of the third297((1, 2), (2, 0)),298// the end of the third row should connect to the beginning of the fourth299((2, 2), (3, 0)),300];301for (page_num, page_entities) in pages_entities.iter().enumerate() {302// Skip Page 2; we are only adding these manual edges for the grid pages.303if page_num == 1 {304continue;305}306for ((entity_a_row, entity_a_col), (entity_b_row, entity_b_col)) in entity_pairs.iter() {307manual_directional_nav_map.add_symmetrical_edge(308page_entities[entity_a_row * 3 + entity_a_col],309page_entities[entity_b_row * 3 + entity_b_col],310CompassOctant::East,311);312}313}314315// Add manual edges within the triangle page (Page 2) between buttons 3 and 4.316// The `AutoNavigationConfig` is set to our desired values, but automatic317// navigation does not connect Button 3 to Button 4, so we have to add318// this navigation manually.319manual_directional_nav_map.add_symmetrical_edge(320pages_entities[1][2],321pages_entities[1][3],322CompassOctant::East,323);324manual_directional_nav_map.add_symmetrical_edge(325pages_entities[1][2],326pages_entities[1][3],327CompassOctant::South,328);329manual_directional_nav_map.add_symmetrical_edge(330pages_entities[1][2],331pages_entities[1][3],332CompassOctant::SouthEast,333);334335// For Page 3, we override the navigation North and South to be inverted.336let mut col_entities = Vec::with_capacity(4);337for col in 0..=2 {338for row in 0..=3 {339col_entities.push(pages_entities[2][row * 3 + col]);340}341manual_directional_nav_map.add_looping_edges(&col_entities, CompassOctant::North);342col_entities.clear();343}344345// Add manual edges between pages.346// When navigating east (right) from the last button of page 1,347// go to the first button of page 2. This edge is symmetrical.348manual_directional_nav_map.add_symmetrical_edge(349pages_entities[0][11],350pages_entities[1][0],351CompassOctant::East,352);353// When navigating south (down) from the last button of page 2,354// go to the first button of page 3. This edge is NOT symmetrical.355// This means going north (up) from the first button of page 3 does356// NOT go to the last button of page 2.357manual_directional_nav_map.add_edge(358pages_entities[1][3],359pages_entities[2][0],360CompassOctant::South,361);362// When navigating west (left) from the first button of page 3,363// go back to the last button of page 2. This edge is NOT symmetrical.364manual_directional_nav_map.add_edge(365pages_entities[2][0],366pages_entities[1][3],367CompassOctant::West,368);369// When navigating east (right) from the last button of page 1,370// go to the first button of page 2. This edge is symmetrical.371manual_directional_nav_map.add_symmetrical_edge(372pages_entities[2][11],373pages_entities[0][0],374CompassOctant::East,375);376377// Set initial focus378input_focus.set(pages_entities[0][0]);379}380381/// Creates the buttons and text for a grid page and places the ids into their382/// respective Vecs in `entities`.383fn setup_buttons_for_grid_page(384commands: &mut Commands,385page_num: usize,386entities: (&mut Vec<Entity>, &mut Vec<Entity>),387) {388let (page_button_entities, text_entities) = entities;389390// Spawn buttons in a grid391// Auto-navigation will automatically configure navigation within rows.392let button_positions = [393// Row 0394[(450.0, 80.0), (650.0, 80.0), (850.0, 80.0)],395// Row 1396[(450.0, 215.0), (650.0, 215.0), (850.0, 215.0)],397// Row 2398[(450.0, 350.0), (650.0, 350.0), (850.0, 350.0)],399// Row 3400[(450.0, 485.0), (650.0, 485.0), (850.0, 485.0)],401];402for (i, row) in button_positions.iter().enumerate() {403for (j, (left, top)) in row.iter().enumerate() {404let button_entity = spawn_auto_nav_button(405commands,406format!("Btn {}-{}", i + 1, j + 1),407left,408top,409page_num,410);411page_button_entities.push(button_entity);412}413}414415// Text describing current page416let current_page_entity = spawn_small_text_node(417commands,418format!("Currently on Page {}", page_num + 1),419650,42020,421Justify::Center,422);423text_entities.push(current_page_entity);424425// Text describing direction to go to the previous page, placed left of the top-left button.426let previous_page = if page_num == 0 { 3 } else { page_num };427let previous_page_entity = spawn_small_text_node(428commands,429format!("Page {} << ", previous_page),430310,431120,432Justify::Right,433);434text_entities.push(previous_page_entity);435436// Text describing direction to go to the next page, placed right of the bottom-right button.437let next_page_entity = spawn_small_text_node(438commands,439format!(">> Page {}", (page_num + 1) % 3 + 1),4401000,441525,442Justify::Left,443);444text_entities.push(next_page_entity);445446// Texts describing that moving right wraps to the next row.447let right_1 = spawn_small_text_node(commands, "> Btn 2-1".into(), 1000, 120, Justify::Left);448let right_2 = spawn_small_text_node(commands, "> Btn 3-1".into(), 1000, 255, Justify::Left);449let right_3 = spawn_small_text_node(commands, "> Btn 4-1".into(), 1000, 390, Justify::Left);450let left_1 = spawn_small_text_node(commands, "Btn 1-3 < ".into(), 310, 255, Justify::Right);451let left_2 = spawn_small_text_node(commands, "Btn 2-3 < ".into(), 310, 390, Justify::Right);452let left_3 = spawn_small_text_node(commands, "Btn 3-3 < ".into(), 310, 525, Justify::Right);453text_entities.push(right_1);454text_entities.push(right_2);455text_entities.push(right_3);456text_entities.push(left_1);457text_entities.push(left_2);458text_entities.push(left_3);459460// For the third page, add a notice about vertical navigation being inverted in the grid.461if page_num == 2 {462let footer_info = commands463.spawn((464Text::new(465"Vertical Navigation has been manually overridden to be inverted! \466^ moves down, and v (down) moves up.",467),468Node {469position_type: PositionType::Absolute,470left: px(450),471top: px(600),472width: px(540),473padding: UiRect::all(px(12)),474..default()475},476TextFont {477font_size: FontSize::Px(20.0),478..default()479},480))481.id();482text_entities.push(footer_info);483}484}485486/// Creates the buttons and text for the triangle page (page 2) and places the ids into their487/// respective Vecs in `entities`.488fn setup_buttons_for_triangle_page(489commands: &mut Commands,490page_num: usize,491entities: (&mut Vec<Entity>, &mut Vec<Entity>),492) {493let button_positions = [494(450.0, 80.0), // top left495(700.0, 80.0), // top right496(575.0, 215.0), // middle497(1050.0, 350.0), // bottom right498];499let (page_button_entities, text_entities) = entities;500for (i, (left, top)) in button_positions.iter().enumerate() {501let button_entity =502spawn_auto_nav_button(commands, format!("Btn {}", i + 1), left, top, page_num);503page_button_entities.push(button_entity);504}505506// Text describing current page507let current_page_entity = spawn_small_text_node(508commands,509format!("Currently on Page {}", page_num + 1),510650,51120,512Justify::Center,513);514text_entities.push(current_page_entity);515516// Text describing direction to go to the previous page, placed left of the top-left button.517let previous_page = if page_num == 0 { 3 } else { page_num };518let previous_page_entity = spawn_small_text_node(519commands,520format!("Page {} << ", previous_page),521310,522120,523Justify::Right,524);525text_entities.push(previous_page_entity);526527// Direction to navigate from button 3 to button 4, placed below center button528let below_button_three_entity =529spawn_small_text_node(commands, "v\nBtn 4".into(), 575, 325, Justify::Center);530text_entities.push(below_button_three_entity);531532// Direction to navigate from button 3 to button 4, placed right of center button533let right_of_button_three_entity =534spawn_small_text_node(commands, "> Btn 4".into(), 735, 255, Justify::Left);535text_entities.push(right_of_button_three_entity);536537// Direction to navigate from button 4 to button 3, placed above bottom right button538let below_button_three_entity =539spawn_small_text_node(commands, "Btn 3\n^".into(), 1050, 300, Justify::Center);540text_entities.push(below_button_three_entity);541542// Direction to navigate from button 4 to button 3, placed left of bottom right button543let right_of_button_three_entity =544spawn_small_text_node(commands, "Btn 3 < ".into(), 910, 390, Justify::Right);545text_entities.push(right_of_button_three_entity);546547// Direction to go to the next page, placed bottom of the bottom-right button.548let next_page_entity = spawn_small_text_node(549commands,550format!("V\nV\nPage {}", (page_num + 1) % 3 + 1),5511050,552460,553Justify::Center,554);555text_entities.push(next_page_entity);556}557558fn spawn_auto_nav_button(559commands: &mut Commands,560text: String,561left: &f64,562top: &f64,563page_num: usize,564) -> Entity {565commands566.spawn((567Button,568Node {569position_type: PositionType::Absolute,570left: px(*left),571top: px(*top),572width: px(140),573height: px(100),574border: UiRect::all(px(4)),575justify_content: JustifyContent::Center,576align_items: AlignItems::Center,577border_radius: BorderRadius::all(px(12)),578..default()579},580Page(page_num),581BackgroundColor(NORMAL_BUTTON_COLORS[page_num].into()),582// Just add this component for automatic navigation583AutoDirectionalNavigation::default(),584ResetTimer::default(),585Name::new(text.clone()),586))587.with_child((588Text::new(text),589TextLayout {590justify: Justify::Center,591..default()592},593))594.id()595}596597fn spawn_small_text_node(598commands: &mut Commands,599text: String,600left: i32,601top: i32,602justify: Justify,603) -> Entity {604commands605.spawn((606Text::new(text),607Node {608position_type: PositionType::Absolute,609left: px(left),610top: px(top),611width: px(140),612padding: UiRect::all(px(12)),613..default()614},615TextFont {616font_size: FontSize::Px(20.0),617..default()618},619TextLayout {620justify,621..default()622},623))624.id()625}626627// Action state and input handling628#[derive(Debug, PartialEq, Eq, Hash)]629enum DirectionalNavigationAction {630Up,631Down,632Left,633Right,634Select,635}636637impl DirectionalNavigationAction {638fn variants() -> Vec<Self> {639vec![640DirectionalNavigationAction::Up,641DirectionalNavigationAction::Down,642DirectionalNavigationAction::Left,643DirectionalNavigationAction::Right,644DirectionalNavigationAction::Select,645]646}647648fn keycode(&self) -> KeyCode {649match self {650DirectionalNavigationAction::Up => KeyCode::ArrowUp,651DirectionalNavigationAction::Down => KeyCode::ArrowDown,652DirectionalNavigationAction::Left => KeyCode::ArrowLeft,653DirectionalNavigationAction::Right => KeyCode::ArrowRight,654DirectionalNavigationAction::Select => KeyCode::Enter,655}656}657658fn gamepad_button(&self) -> GamepadButton {659match self {660DirectionalNavigationAction::Up => GamepadButton::DPadUp,661DirectionalNavigationAction::Down => GamepadButton::DPadDown,662DirectionalNavigationAction::Left => GamepadButton::DPadLeft,663DirectionalNavigationAction::Right => GamepadButton::DPadRight,664DirectionalNavigationAction::Select => GamepadButton::South,665}666}667}668669#[derive(Default, Resource)]670struct ActionState {671pressed_actions: HashSet<DirectionalNavigationAction>,672}673674fn process_inputs(675mut action_state: ResMut<ActionState>,676keyboard_input: Res<ButtonInput<KeyCode>>,677gamepad_input: Query<&Gamepad>,678) {679action_state.pressed_actions.clear();680681for action in DirectionalNavigationAction::variants() {682if keyboard_input.just_pressed(action.keycode()) {683action_state.pressed_actions.insert(action);684}685}686687for gamepad in gamepad_input.iter() {688for action in DirectionalNavigationAction::variants() {689if gamepad.just_pressed(action.gamepad_button()) {690action_state.pressed_actions.insert(action);691}692}693}694}695696fn navigate(697action_state: Res<ActionState>,698parent_query: Query<&ChildOf>,699mut visibility_query: Query<&mut Visibility>,700mut auto_directional_navigator: AutoDirectionalNavigator,701) {702let net_east_west = action_state703.pressed_actions704.contains(&DirectionalNavigationAction::Right) as i8705- action_state706.pressed_actions707.contains(&DirectionalNavigationAction::Left) as i8;708709let net_north_south = action_state710.pressed_actions711.contains(&DirectionalNavigationAction::Up) as i8712- action_state713.pressed_actions714.contains(&DirectionalNavigationAction::Down) as i8;715716// Use Dir2::from_xy to convert input to direction, then convert to CompassOctant717let maybe_direction = Dir2::from_xy(net_east_west as f32, net_north_south as f32)718.ok()719.map(CompassOctant::from);720721// Store the previous focus in case navigation switches pages.722let previous_focus = auto_directional_navigator.input_focus();723if let Some(direction) = maybe_direction {724match auto_directional_navigator.navigate(direction) {725Ok(new_focus) => {726// Successfully navigated!727728// If navigation switches between pages, change the visibilities of pages729if let Ok(current_child_of) = parent_query.get(new_focus)730&& let Ok(mut current_page_visibility) =731visibility_query.get_mut(current_child_of.parent())732{733*current_page_visibility = Visibility::Visible;734735if let Some(previous_focus_entity) = previous_focus736&& let Ok(previous_child_of) = parent_query.get(previous_focus_entity)737&& previous_child_of.parent() != current_child_of.parent()738&& let Ok(mut previous_page_visibility) =739visibility_query.get_mut(previous_child_of.parent())740{741*previous_page_visibility = Visibility::Hidden;742}743}744}745Err(_e) => {746// Navigation failed (no neighbor in that direction)747}748}749}750}751752fn update_focus_display(753input_focus: Res<InputFocus>,754button_query: Query<&Name, With<Button>>,755mut display_query: Query<&mut Text, With<FocusDisplay>>,756) {757if let Ok(mut text) = display_query.single_mut() {758if let Some(focused_entity) = input_focus.0 {759if let Ok(name) = button_query.get(focused_entity) {760**text = format!("Focused: {}", name);761} else {762**text = "Focused: Unknown".to_string();763}764} else {765**text = "Focused: None".to_string();766}767}768}769770fn update_key_display(771keyboard_input: Res<ButtonInput<KeyCode>>,772gamepad_input: Query<&Gamepad>,773mut display_query: Query<&mut Text, With<KeyDisplay>>,774) {775if let Ok(mut text) = display_query.single_mut() {776// Check for keyboard inputs777for action in DirectionalNavigationAction::variants() {778if keyboard_input.just_pressed(action.keycode()) {779let key_name = match action {780DirectionalNavigationAction::Up => "Up Arrow",781DirectionalNavigationAction::Down => "Down Arrow",782DirectionalNavigationAction::Left => "Left Arrow",783DirectionalNavigationAction::Right => "Right Arrow",784DirectionalNavigationAction::Select => "Enter",785};786**text = format!("Last Key: {}", key_name);787return;788}789}790791// Check for gamepad inputs792for gamepad in gamepad_input.iter() {793for action in DirectionalNavigationAction::variants() {794if gamepad.just_pressed(action.gamepad_button()) {795let button_name = match action {796DirectionalNavigationAction::Up => "D-Pad Up",797DirectionalNavigationAction::Down => "D-Pad Down",798DirectionalNavigationAction::Left => "D-Pad Left",799DirectionalNavigationAction::Right => "D-Pad Right",800DirectionalNavigationAction::Select => "A Button",801};802**text = format!("Last Key: {}", button_name);803return;804}805}806}807}808}809810fn highlight_focused_element(811input_focus: Res<InputFocus>,812input_focus_visible: Res<InputFocusVisible>,813mut query: Query<(Entity, &mut BorderColor, &Page)>,814) {815for (entity, mut border_color, page) in query.iter_mut() {816if input_focus.0 == Some(entity) && input_focus_visible.0 {817*border_color = BorderColor::all(FOCUSED_BORDER_COLORS[page.0]);818} else {819*border_color = BorderColor::DEFAULT;820}821}822}823824fn interact_with_focused_button(825action_state: Res<ActionState>,826input_focus: Res<InputFocus>,827mut commands: Commands,828) {829if action_state830.pressed_actions831.contains(&DirectionalNavigationAction::Select)832&& let Some(focused_entity) = input_focus.0833{834commands.trigger(Pointer::<Click> {835entity: focused_entity,836pointer_id: PointerId::Mouse,837pointer_location: Location {838target: NormalizedRenderTarget::None {839width: 0,840height: 0,841},842position: Vec2::ZERO,843},844event: Click {845button: PointerButton::Primary,846hit: HitData {847camera: Entity::PLACEHOLDER,848depth: 0.0,849position: None,850normal: None,851},852duration: Duration::from_secs_f32(0.1),853},854});855}856}857858859