Polish and improve docs for bevy_input_focus
(#16887)
# Objective `bevy_input_focus` needs some love before we ship it to users. There's a few missing helper methods, the docs could be improved, and `AutoFocus` should be more generally available. ## Solution The changes here are broken down by commit, and should generally be uncontroversial. The ones to focus on during review are: - Make navigate take a & InputFocus argument: this makes the intended pattern clearer to users - Remove TabGroup requirement from `AutoFocus`: I want auto-focusing even with gamepad-style focus navigation! - Handle case where tab group is None more gracefully: I think we can try harder to provide something usable, and shouldn't just fail to navigate ## Testing The `tab_navigation` example continues to work.
This commit is contained in:
parent
20049d4c34
commit
d8796ae8b6
20
crates/bevy_input_focus/src/autofocus.rs
Normal file
20
crates/bevy_input_focus/src/autofocus.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//! Contains the [`AutoFocus`] component and related machinery.
|
||||||
|
|
||||||
|
use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld};
|
||||||
|
|
||||||
|
use crate::SetInputFocus;
|
||||||
|
|
||||||
|
/// Indicates that this widget should automatically receive [`InputFocus`](crate::InputFocus).
|
||||||
|
///
|
||||||
|
/// This can be useful for things like dialog boxes, the first text input in a form,
|
||||||
|
/// or the first button in a game menu.
|
||||||
|
///
|
||||||
|
/// The focus is swapped when this component is added
|
||||||
|
/// or an entity with this component is spawned.
|
||||||
|
#[derive(Debug, Default, Component, Copy, Clone)]
|
||||||
|
#[component(on_add = on_auto_focus_added)]
|
||||||
|
pub struct AutoFocus;
|
||||||
|
|
||||||
|
fn on_auto_focus_added(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||||
|
world.set_input_focus(entity);
|
||||||
|
}
|
@ -8,16 +8,20 @@
|
|||||||
//! Keyboard focus system for Bevy.
|
//! Keyboard focus system for Bevy.
|
||||||
//!
|
//!
|
||||||
//! This crate provides a system for managing input focus in Bevy applications, including:
|
//! This crate provides a system for managing input focus in Bevy applications, including:
|
||||||
//! * A resource for tracking which entity has input focus.
|
//! * [`InputFocus`], a resource for tracking which entity has input focus.
|
||||||
//! * Methods for getting and setting input focus.
|
//! * Methods for getting and setting input focus via [`SetInputFocus`], [`InputFocus`] and [`IsFocusedHelper`].
|
||||||
//! * Event definitions for triggering bubble-able keyboard input events to the focused entity.
|
//! * A generic [`FocusedInput`] event for input events which bubble up from the focused entity.
|
||||||
//! * A system for dispatching keyboard input events to the focused entity.
|
|
||||||
//!
|
//!
|
||||||
//! This crate does *not* provide any integration with UI widgets, or provide functions for
|
//! This crate does *not* provide any integration with UI widgets: this is the responsibility of the widget crate,
|
||||||
//! tab navigation or gamepad-based focus navigation, as those are typically application-specific.
|
//! which should depend on [`bevy_input_focus`](crate).
|
||||||
|
|
||||||
pub mod tab_navigation;
|
pub mod tab_navigation;
|
||||||
|
|
||||||
|
// This module is too small / specific to be exported by the crate,
|
||||||
|
// but it's nice to have it separate for code organization.
|
||||||
|
mod autofocus;
|
||||||
|
pub use autofocus::*;
|
||||||
|
|
||||||
use bevy_app::{App, Plugin, PreUpdate, Startup};
|
use bevy_app::{App, Plugin, PreUpdate, Startup};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::*, query::QueryData, system::SystemParam, traversal::Traversal, world::DeferredWorld,
|
prelude::*, query::QueryData, system::SystemParam, traversal::Traversal, world::DeferredWorld,
|
||||||
@ -29,11 +33,36 @@ use core::fmt::Debug;
|
|||||||
|
|
||||||
/// Resource representing which entity has input focus, if any. Keyboard events will be
|
/// Resource representing which entity has input focus, if any. Keyboard events will be
|
||||||
/// dispatched to the current focus entity, or to the primary window if no entity has focus.
|
/// dispatched to the current focus entity, or to the primary window if no entity has focus.
|
||||||
#[derive(Clone, Debug, Resource)]
|
#[derive(Clone, Debug, Default, Resource)]
|
||||||
pub struct InputFocus(pub Option<Entity>);
|
pub struct InputFocus(pub Option<Entity>);
|
||||||
|
|
||||||
/// Resource representing whether the input focus indicator should be visible. It's up to the
|
impl InputFocus {
|
||||||
/// current focus navigation system to set this resource. For a desktop/web style of user interface
|
/// Create a new [`InputFocus`] resource with the given entity.
|
||||||
|
///
|
||||||
|
/// This is mostly useful for tests.
|
||||||
|
pub const fn from_entity(entity: Entity) -> Self {
|
||||||
|
Self(Some(entity))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the entity with input focus.
|
||||||
|
pub const fn set(&mut self, entity: Entity) {
|
||||||
|
self.0 = Some(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the entity with input focus, if any.
|
||||||
|
pub const fn get(&self) -> Option<Entity> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears input focus.
|
||||||
|
pub const fn clear(&mut self) {
|
||||||
|
self.0 = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource representing whether the input focus indicator should be visible on UI elements.
|
||||||
|
///
|
||||||
|
/// It's up to the current focus navigation system to set this resource. For a desktop/web style of user interface
|
||||||
/// this would be set to true when the user presses the tab key, and set to false when the user
|
/// this would be set to true when the user presses the tab key, and set to false when the user
|
||||||
/// clicks on a different element.
|
/// clicks on a different element.
|
||||||
#[derive(Clone, Debug, Resource)]
|
#[derive(Clone, Debug, Resource)]
|
||||||
@ -43,6 +72,8 @@ pub struct InputFocusVisible(pub bool);
|
|||||||
///
|
///
|
||||||
/// These methods are equivalent to modifying the [`InputFocus`] resource directly,
|
/// These methods are equivalent to modifying the [`InputFocus`] resource directly,
|
||||||
/// but only take effect when commands are applied.
|
/// but only take effect when commands are applied.
|
||||||
|
///
|
||||||
|
/// See [`IsFocused`] for methods to check if an entity has focus.
|
||||||
pub trait SetInputFocus {
|
pub trait SetInputFocus {
|
||||||
/// Set input focus to the given entity.
|
/// Set input focus to the given entity.
|
||||||
///
|
///
|
||||||
@ -151,8 +182,10 @@ impl<E: Event + Clone> Traversal<FocusedInput<E>> for WindowTraversal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plugin which registers the system for dispatching keyboard events based on focus and
|
/// Plugin which sets up systems for dispatching bubbling keyboard and gamepad button events to the focused entity.
|
||||||
/// hover state.
|
///
|
||||||
|
/// To add bubbling to your own input events, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
|
||||||
|
/// as described in the docs for [`FocusedInput`].
|
||||||
pub struct InputDispatchPlugin;
|
pub struct InputDispatchPlugin;
|
||||||
|
|
||||||
impl Plugin for InputDispatchPlugin {
|
impl Plugin for InputDispatchPlugin {
|
||||||
@ -198,19 +231,19 @@ pub fn dispatch_focused_input<E: Event + Clone>(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
if let Ok(window) = windows.get_single() {
|
if let Ok(window) = windows.get_single() {
|
||||||
// If an element has keyboard focus, then dispatch the key event to that element.
|
// If an element has keyboard focus, then dispatch the input event to that element.
|
||||||
if let Some(focus_elt) = focus.0 {
|
if let Some(focused_entity) = focus.0 {
|
||||||
for ev in key_events.read() {
|
for ev in key_events.read() {
|
||||||
commands.trigger_targets(
|
commands.trigger_targets(
|
||||||
FocusedInput {
|
FocusedInput {
|
||||||
input: ev.clone(),
|
input: ev.clone(),
|
||||||
window,
|
window,
|
||||||
},
|
},
|
||||||
focus_elt,
|
focused_entity,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If no element has input focus, then dispatch the key event to the primary window.
|
// If no element has input focus, then dispatch the input event to the primary window.
|
||||||
// There should be only one primary window.
|
// There should be only one primary window.
|
||||||
for ev in key_events.read() {
|
for ev in key_events.read() {
|
||||||
commands.trigger_targets(
|
commands.trigger_targets(
|
||||||
@ -225,27 +258,36 @@ pub fn dispatch_focused_input<E: Event + Clone>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait which defines methods to check if an entity currently has focus. This is implemented
|
/// Trait which defines methods to check if an entity currently has focus.
|
||||||
/// for [`World`] and [`IsFocusedHelper`].
|
///
|
||||||
|
/// This is implemented for [`World`] and [`IsFocusedHelper`].
|
||||||
/// [`DeferredWorld`] indirectly implements it through [`Deref`].
|
/// [`DeferredWorld`] indirectly implements it through [`Deref`].
|
||||||
///
|
///
|
||||||
|
/// For use within systems, use [`IsFocusedHelper`].
|
||||||
|
///
|
||||||
|
/// See [`SetInputFocus`] for methods to set and clear input focus.
|
||||||
|
///
|
||||||
/// [`Deref`]: std::ops::Deref
|
/// [`Deref`]: std::ops::Deref
|
||||||
pub trait IsFocused {
|
pub trait IsFocused {
|
||||||
/// Returns true if the given entity has input focus.
|
/// Returns true if the given entity has input focus.
|
||||||
fn is_focused(&self, entity: Entity) -> bool;
|
fn is_focused(&self, entity: Entity) -> bool;
|
||||||
|
|
||||||
/// Returns true if the given entity or any of its descendants has input focus.
|
/// Returns true if the given entity or any of its descendants has input focus.
|
||||||
|
///
|
||||||
|
/// Note that for unusual layouts, the focus may not be within the entity's visual bounds.
|
||||||
fn is_focus_within(&self, entity: Entity) -> bool;
|
fn is_focus_within(&self, entity: Entity) -> bool;
|
||||||
|
|
||||||
/// Returns true if the given entity has input focus and the focus indicator is visible.
|
/// Returns true if the given entity has input focus and the focus indicator should be visible.
|
||||||
fn is_focus_visible(&self, entity: Entity) -> bool;
|
fn is_focus_visible(&self, entity: Entity) -> bool;
|
||||||
|
|
||||||
/// Returns true if the given entity, or any descendant, has input focus and the focus
|
/// Returns true if the given entity, or any descendant, has input focus and the focus
|
||||||
/// indicator is visible.
|
/// indicator should be visible.
|
||||||
fn is_focus_within_visible(&self, entity: Entity) -> bool;
|
fn is_focus_within_visible(&self, entity: Entity) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// System param that helps get information about the current focused entity.
|
/// A system param that helps get information about the current focused entity.
|
||||||
|
///
|
||||||
|
/// When working with the entire [`World`], consider using the [`IsFocused`] instead.
|
||||||
#[derive(SystemParam)]
|
#[derive(SystemParam)]
|
||||||
pub struct IsFocusedHelper<'w, 's> {
|
pub struct IsFocusedHelper<'w, 's> {
|
||||||
parent_query: Query<'w, 's, &'static Parent>,
|
parent_query: Query<'w, 's, &'static Parent>,
|
||||||
|
@ -9,31 +9,27 @@
|
|||||||
//! * An index < 0 means that the entity is not focusable via sequential navigation, but
|
//! * An index < 0 means that the entity is not focusable via sequential navigation, but
|
||||||
//! can still be focused via direct selection.
|
//! can still be focused via direct selection.
|
||||||
//!
|
//!
|
||||||
//! Tabbable entities must be descendants of a `TabGroup` entity, which is a component that
|
//! Tabbable entities must be descendants of a [`TabGroup`] entity, which is a component that
|
||||||
//! marks a tree of entities as containing tabbable elements. The order of tab groups
|
//! marks a tree of entities as containing tabbable elements. The order of tab groups
|
||||||
//! is determined by the `order` field, with lower orders being tabbed first. Modal tab groups
|
//! is determined by the [`TabGroup::order`] field, with lower orders being tabbed first. Modal tab groups
|
||||||
//! are used for ui elements that should only tab within themselves, such as modal dialog boxes.
|
//! are used for ui elements that should only tab within themselves, such as modal dialog boxes.
|
||||||
//!
|
//!
|
||||||
//! There are several different ways to use this module. To enable automatic tabbing, add the
|
//! To enable automatic tabbing, add the
|
||||||
//! `TabNavigationPlugin` to your app. (Make sure you also have `InputDispatchPlugin` installed).
|
//! [`TabNavigationPlugin`] and [`InputDispatchPlugin`](crate::InputDispatchPlugin) to your app.
|
||||||
//! This will install a keyboard event observer on the primary window which automatically handles
|
//! This will install a keyboard event observer on the primary window which automatically handles
|
||||||
//! tab navigation for you.
|
//! tab navigation for you.
|
||||||
//!
|
//!
|
||||||
//! Alternatively, if you want to have more control over tab navigation, or are using an event
|
//! Alternatively, if you want to have more control over tab navigation, or are using an input-action-mapping framework,
|
||||||
//! mapping framework such as LWIM, you can use the `TabNavigation` helper object directly instead.
|
//! you can use the [`TabNavigation`] system parameter directly instead.
|
||||||
//! This object can be injected into your systems, and provides a `navigate` method which can be
|
//! This object can be injected into your systems, and provides a [`navigate`](`TabNavigation::navigate`) method which can be
|
||||||
//! used to navigate between focusable entities.
|
//! used to navigate between focusable entities.
|
||||||
//!
|
|
||||||
//! This module also provides `AutoFocus`, a component which can be added to an entity to
|
|
||||||
//! automatically focus it when it is added to the world.
|
|
||||||
use bevy_app::{App, Plugin, Startup};
|
use bevy_app::{App, Plugin, Startup};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::{Component, ComponentId},
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
observer::Trigger,
|
observer::Trigger,
|
||||||
query::{With, Without},
|
query::{With, Without},
|
||||||
system::{Commands, Query, Res, ResMut, SystemParam},
|
system::{Commands, Query, Res, ResMut, SystemParam},
|
||||||
world::DeferredWorld,
|
|
||||||
};
|
};
|
||||||
use bevy_hierarchy::{Children, HierarchyQueryExt, Parent};
|
use bevy_hierarchy::{Children, HierarchyQueryExt, Parent};
|
||||||
use bevy_input::{
|
use bevy_input::{
|
||||||
@ -43,7 +39,7 @@ use bevy_input::{
|
|||||||
use bevy_utils::tracing::warn;
|
use bevy_utils::tracing::warn;
|
||||||
use bevy_window::PrimaryWindow;
|
use bevy_window::PrimaryWindow;
|
||||||
|
|
||||||
use crate::{FocusedInput, InputFocus, InputFocusVisible, SetInputFocus};
|
use crate::{FocusedInput, InputFocus, InputFocusVisible};
|
||||||
|
|
||||||
/// A component which indicates that an entity wants to participate in tab navigation.
|
/// A component which indicates that an entity wants to participate in tab navigation.
|
||||||
///
|
///
|
||||||
@ -52,10 +48,6 @@ use crate::{FocusedInput, InputFocus, InputFocusVisible, SetInputFocus};
|
|||||||
#[derive(Debug, Default, Component, Copy, Clone)]
|
#[derive(Debug, Default, Component, Copy, Clone)]
|
||||||
pub struct TabIndex(pub i32);
|
pub struct TabIndex(pub i32);
|
||||||
|
|
||||||
/// Indicates that this widget should automatically receive focus when it's added.
|
|
||||||
#[derive(Debug, Default, Component, Copy, Clone)]
|
|
||||||
pub struct AutoFocus;
|
|
||||||
|
|
||||||
/// A component used to mark a tree of entities as containing tabbable elements.
|
/// A component used to mark a tree of entities as containing tabbable elements.
|
||||||
#[derive(Debug, Default, Component, Copy, Clone)]
|
#[derive(Debug, Default, Component, Copy, Clone)]
|
||||||
pub struct TabGroup {
|
pub struct TabGroup {
|
||||||
@ -87,7 +79,9 @@ impl TabGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigation action for tabbing.
|
/// A navigation action for tabbing.
|
||||||
|
///
|
||||||
|
/// These values are consumed by the [`TabNavigation`] system param.
|
||||||
pub enum NavAction {
|
pub enum NavAction {
|
||||||
/// Navigate to the next focusable entity, wrapping around to the beginning if at the end.
|
/// Navigate to the next focusable entity, wrapping around to the beginning if at the end.
|
||||||
Next,
|
Next,
|
||||||
@ -120,6 +114,8 @@ pub struct TabNavigation<'w, 's> {
|
|||||||
impl TabNavigation<'_, '_> {
|
impl TabNavigation<'_, '_> {
|
||||||
/// Navigate to the next focusable entity.
|
/// Navigate to the next focusable entity.
|
||||||
///
|
///
|
||||||
|
/// Focusable entities are determined by the presence of the [`TabIndex`] component.
|
||||||
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// * `focus`: The current focus entity, or `None` if no entity has focus.
|
/// * `focus`: The current focus entity, or `None` if no entity has focus.
|
||||||
/// * `action`: Whether to select the next, previous, first, or last focusable entity.
|
/// * `action`: Whether to select the next, previous, first, or last focusable entity.
|
||||||
@ -128,7 +124,7 @@ impl TabNavigation<'_, '_> {
|
|||||||
/// or last focusable entity, depending on the direction of navigation. For example, if
|
/// or last focusable entity, depending on the direction of navigation. For example, if
|
||||||
/// `action` is `Next` and no focusable entities are found, then this function will return
|
/// `action` is `Next` and no focusable entities are found, then this function will return
|
||||||
/// the first focusable entity.
|
/// the first focusable entity.
|
||||||
pub fn navigate(&self, focus: Option<Entity>, action: NavAction) -> Option<Entity> {
|
pub fn navigate(&self, focus: &InputFocus, action: NavAction) -> Option<Entity> {
|
||||||
// If there are no tab groups, then there are no focusable entities.
|
// If there are no tab groups, then there are no focusable entities.
|
||||||
if self.tabgroup_query.is_empty() {
|
if self.tabgroup_query.is_empty() {
|
||||||
warn!("No tab groups found");
|
warn!("No tab groups found");
|
||||||
@ -137,7 +133,7 @@ impl TabNavigation<'_, '_> {
|
|||||||
|
|
||||||
// Start by identifying which tab group we are in. Mainly what we want to know is if
|
// Start by identifying which tab group we are in. Mainly what we want to know is if
|
||||||
// we're in a modal group.
|
// we're in a modal group.
|
||||||
let tabgroup = focus.and_then(|focus_ent| {
|
let tabgroup = focus.0.and_then(|focus_ent| {
|
||||||
self.parent_query
|
self.parent_query
|
||||||
.iter_ancestors(focus_ent)
|
.iter_ancestors(focus_ent)
|
||||||
.find_map(|entity| {
|
.find_map(|entity| {
|
||||||
@ -148,9 +144,8 @@ impl TabNavigation<'_, '_> {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if focus.is_some() && tabgroup.is_none() {
|
if focus.0.is_some() && tabgroup.is_none() {
|
||||||
warn!("No tab group found for focus entity");
|
warn!("No tab group found for focus entity. Users will not be able to navigate back to this entity.");
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.navigate_in_group(tabgroup, focus, action)
|
self.navigate_in_group(tabgroup, focus, action)
|
||||||
@ -159,7 +154,7 @@ impl TabNavigation<'_, '_> {
|
|||||||
fn navigate_in_group(
|
fn navigate_in_group(
|
||||||
&self,
|
&self,
|
||||||
tabgroup: Option<(Entity, &TabGroup)>,
|
tabgroup: Option<(Entity, &TabGroup)>,
|
||||||
focus: Option<Entity>,
|
focus: &InputFocus,
|
||||||
action: NavAction,
|
action: NavAction,
|
||||||
) -> Option<Entity> {
|
) -> Option<Entity> {
|
||||||
// List of all focusable entities found.
|
// List of all focusable entities found.
|
||||||
@ -201,7 +196,7 @@ impl TabNavigation<'_, '_> {
|
|||||||
// Stable sort by tabindex
|
// Stable sort by tabindex
|
||||||
focusable.sort_by(compare_tab_indices);
|
focusable.sort_by(compare_tab_indices);
|
||||||
|
|
||||||
let index = focusable.iter().position(|e| Some(e.0) == focus);
|
let index = focusable.iter().position(|e| Some(e.0) == focus.0);
|
||||||
let count = focusable.len();
|
let count = focusable.len();
|
||||||
let next = match (index, action) {
|
let next = match (index, action) {
|
||||||
(Some(idx), NavAction::Next) => (idx + 1).rem_euclid(count),
|
(Some(idx), NavAction::Next) => (idx + 1).rem_euclid(count),
|
||||||
@ -247,15 +242,12 @@ fn compare_tab_indices(a: &(Entity, TabIndex), b: &(Entity, TabIndex)) -> core::
|
|||||||
a.1 .0.cmp(&b.1 .0)
|
a.1 .0.cmp(&b.1 .0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plugin for handling keyboard input.
|
/// Plugin for navigating between focusable entities using keyboard input.
|
||||||
pub struct TabNavigationPlugin;
|
pub struct TabNavigationPlugin;
|
||||||
|
|
||||||
impl Plugin for TabNavigationPlugin {
|
impl Plugin for TabNavigationPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Startup, setup_tab_navigation);
|
app.add_systems(Startup, setup_tab_navigation);
|
||||||
app.world_mut()
|
|
||||||
.register_component_hooks::<AutoFocus>()
|
|
||||||
.on_add(on_auto_focus_added);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +275,7 @@ pub fn handle_tab_navigation(
|
|||||||
&& !key_event.repeat
|
&& !key_event.repeat
|
||||||
{
|
{
|
||||||
let next = nav.navigate(
|
let next = nav.navigate(
|
||||||
focus.0,
|
&focus,
|
||||||
if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) {
|
if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) {
|
||||||
NavAction::Previous
|
NavAction::Previous
|
||||||
} else {
|
} else {
|
||||||
@ -298,12 +290,6 @@ pub fn handle_tab_navigation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_auto_focus_added(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
|
||||||
if world.entity(entity).contains::<TabIndex>() {
|
|
||||||
world.set_input_focus(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bevy_ecs::system::SystemState;
|
use bevy_ecs::system::SystemState;
|
||||||
@ -326,16 +312,18 @@ mod tests {
|
|||||||
assert_eq!(tab_navigation.tabgroup_query.iter().count(), 1);
|
assert_eq!(tab_navigation.tabgroup_query.iter().count(), 1);
|
||||||
assert_eq!(tab_navigation.tabindex_query.iter().count(), 2);
|
assert_eq!(tab_navigation.tabindex_query.iter().count(), 2);
|
||||||
|
|
||||||
let next_entity = tab_navigation.navigate(Some(tab_entity_1), NavAction::Next);
|
let next_entity =
|
||||||
|
tab_navigation.navigate(&InputFocus::from_entity(tab_entity_1), NavAction::Next);
|
||||||
assert_eq!(next_entity, Some(tab_entity_2));
|
assert_eq!(next_entity, Some(tab_entity_2));
|
||||||
|
|
||||||
let prev_entity = tab_navigation.navigate(Some(tab_entity_2), NavAction::Previous);
|
let prev_entity =
|
||||||
|
tab_navigation.navigate(&InputFocus::from_entity(tab_entity_2), NavAction::Previous);
|
||||||
assert_eq!(prev_entity, Some(tab_entity_1));
|
assert_eq!(prev_entity, Some(tab_entity_1));
|
||||||
|
|
||||||
let first_entity = tab_navigation.navigate(None, NavAction::First);
|
let first_entity = tab_navigation.navigate(&InputFocus::default(), NavAction::First);
|
||||||
assert_eq!(first_entity, Some(tab_entity_1));
|
assert_eq!(first_entity, Some(tab_entity_1));
|
||||||
|
|
||||||
let last_entity = tab_navigation.navigate(None, NavAction::Last);
|
let last_entity = tab_navigation.navigate(&InputFocus::default(), NavAction::Last);
|
||||||
assert_eq!(last_entity, Some(tab_entity_2));
|
assert_eq!(last_entity, Some(tab_entity_2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user