bevy/crates/bevy_input_focus/src/lib.rs
Carter Anderson 21f1e3045c
Relationships (non-fragmenting, one-to-many) (#17398)
This adds support for one-to-many non-fragmenting relationships (with
planned paths for fragmenting and non-fragmenting many-to-many
relationships). "Non-fragmenting" means that entities with the same
relationship type, but different relationship targets, are not forced
into separate tables (which would cause "table fragmentation").

Functionally, this fills a similar niche as the current Parent/Children
system. The biggest differences are:

1. Relationships have simpler internals and significantly improved
performance and UX. Commands and specialized APIs are no longer
necessary to keep everything in sync. Just spawn entities with the
relationship components you want and everything "just works".
2. Relationships are generalized. Bevy can provide additional built in
relationships, and users can define their own.

**REQUEST TO REVIEWERS**: _please don't leave top level comments and
instead comment on specific lines of code. That way we can take
advantage of threaded discussions. Also dont leave comments simply
pointing out CI failures as I can read those just fine._

## Built on top of what we have

Relationships are implemented on top of the Bevy ECS features we already
have: components, immutability, and hooks. This makes them immediately
compatible with all of our existing (and future) APIs for querying,
spawning, removing, scenes, reflection, etc. The fewer specialized APIs
we need to build, maintain, and teach, the better.

## Why focus on one-to-many non-fragmenting first?

1. This allows us to improve Parent/Children relationships immediately,
in a way that is reasonably uncontroversial. Switching our hierarchy to
fragmenting relationships would have significant performance
implications. ~~Flecs is heavily considering a switch to non-fragmenting
relations after careful considerations of the performance tradeoffs.~~
_(Correction from @SanderMertens: Flecs is implementing non-fragmenting
storage specialized for asset hierarchies, where asset hierarchies are
many instances of small trees that have a well defined structure)_
2. Adding generalized one-to-many relationships is currently a priority
for the [Next Generation Scene / UI
effort](https://github.com/bevyengine/bevy/discussions/14437).
Specifically, we're interested in building reactions and observers on
top.

## The changes

This PR does the following:

1. Adds a generic one-to-many Relationship system
3. Ports the existing Parent/Children system to Relationships, which now
lives in `bevy_ecs::hierarchy`. The old `bevy_hierarchy` crate has been
removed.
4. Adds on_despawn component hooks
5. Relationships can opt-in to "despawn descendants" behavior, meaning
that the entire relationship hierarchy is despawned when
`entity.despawn()` is called. The built in Parent/Children hierarchies
enable this behavior, and `entity.despawn_recursive()` has been removed.
6. `world.spawn` now applies commands after spawning. This ensures that
relationship bookkeeping happens immediately and removes the need to
manually flush. This is in line with the equivalent behaviors recently
added to the other APIs (ex: insert).
7. Removes the ValidParentCheckPlugin (system-driven / poll based) in
favor of a `validate_parent_has_component` hook.

## Using Relationships

The `Relationship` trait looks like this:

```rust
pub trait Relationship: Component + Sized {
    type RelationshipSources: RelationshipSources<Relationship = Self>;
    fn get(&self) -> Entity;
    fn from(entity: Entity) -> Self;
}
```

A relationship is a component that:

1. Is a simple wrapper over a "target" Entity.
2. Has a corresponding `RelationshipSources` component, which is a
simple wrapper over a collection of entities. Every "target entity"
targeted by a "source entity" with a `Relationship` has a
`RelationshipSources` component, which contains every "source entity"
that targets it.

For example, the `Parent` component (as it currently exists in Bevy) is
the `Relationship` component and the entity containing the Parent is the
"source entity". The entity _inside_ the `Parent(Entity)` component is
the "target entity". And that target entity has a `Children` component
(which implements `RelationshipSources`).

In practice, the Parent/Children relationship looks like this:

```rust
#[derive(Relationship)]
#[relationship(relationship_sources = Children)]
pub struct Parent(pub Entity);

#[derive(RelationshipSources)]
#[relationship_sources(relationship = Parent)]
pub struct Children(Vec<Entity>);
```

The Relationship and RelationshipSources derives automatically implement
Component with the relevant configuration (namely, the hooks necessary
to keep everything in sync).

The most direct way to add relationships is to spawn entities with
relationship components:

```rust
let a = world.spawn_empty().id();
let b = world.spawn(Parent(a)).id();

assert_eq!(world.entity(a).get::<Children>().unwrap(), &[b]);
```

There are also convenience APIs for spawning more than one entity with
the same relationship:

```rust
world.spawn_empty().with_related::<Children>(|s| {
    s.spawn_empty();
    s.spawn_empty();
})
```

The existing `with_children` API is now a simpler wrapper over
`with_related`. This makes this change largely non-breaking for existing
spawn patterns.

```rust
world.spawn_empty().with_children(|s| {
    s.spawn_empty();
    s.spawn_empty();
})
```

There are also other relationship APIs, such as `add_related` and
`despawn_related`.

## Automatic recursive despawn via the new on_despawn hook

`RelationshipSources` can opt-in to "despawn descendants" behavior,
which will despawn all related entities in the relationship hierarchy:

```rust
#[derive(RelationshipSources)]
#[relationship_sources(relationship = Parent, despawn_descendants)]
pub struct Children(Vec<Entity>);
```

This means that `entity.despawn_recursive()` is no longer required.
Instead, just use `entity.despawn()` and the relevant related entities
will also be despawned.

To despawn an entity _without_ despawning its parent/child descendants,
you should remove the `Children` component first, which will also remove
the related `Parent` components:

```rust
entity
    .remove::<Children>()
    .despawn()
```

This builds on the on_despawn hook introduced in this PR, which is fired
when an entity is despawned (before other hooks).

## Relationships are the source of truth

`Relationship` is the _single_ source of truth component.
`RelationshipSources` is merely a reflection of what all the
`Relationship` components say. By embracing this, we are able to
significantly improve the performance of the system as a whole. We can
rely on component lifecycles to protect us against duplicates, rather
than needing to scan at runtime to ensure entities don't already exist
(which results in quadratic runtime). A single source of truth gives us
constant-time inserts. This does mean that we cannot directly spawn
populated `Children` components (or directly add or remove entities from
those components). I personally think this is a worthwhile tradeoff,
both because it makes the performance much better _and_ because it means
theres exactly one way to do things (which is a philosophy we try to
employ for Bevy APIs).

As an aside: treating both sides of the relationship as "equivalent
source of truth relations" does enable building simple and flexible
many-to-many relationships. But this introduces an _inherent_ need to
scan (or hash) to protect against duplicates.
[`evergreen_relations`](https://github.com/EvergreenNest/evergreen_relations)
has a very nice implementation of the "symmetrical many-to-many"
approach. Unfortunately I think the performance issues inherent to that
approach make it a poor choice for Bevy's default relationship system.

## Followup Work

* Discuss renaming `Parent` to `ChildOf`. I refrained from doing that in
this PR to keep the diff reasonable, but I'm personally biased toward
this change (and using that naming pattern generally for relationships).
* [Improved spawning
ergonomics](https://github.com/bevyengine/bevy/discussions/16920)
* Consider adding relationship observers/triggers for "relationship
targets" whenever a source is added or removed. This would replace the
current "hierarchy events" system, which is unused upstream but may have
existing users downstream. I think triggers are the better fit for this
than a buffered event queue, and would prefer not to add that back.
* Fragmenting relations: My current idea hinges on the introduction of
"value components" (aka: components whose type _and_ value determines
their ComponentId, via something like Hashing / PartialEq). By labeling
a Relationship component such as `ChildOf(Entity)` as a "value
component", `ChildOf(e1)` and `ChildOf(e2)` would be considered
"different components". This makes the transition between fragmenting
and non-fragmenting a single flag, and everything else continues to work
as expected.
* Many-to-many support
* Non-fragmenting: We can expand Relationship to be a list of entities
instead of a single entity. I have largely already written the code for
this.
* Fragmenting: With the "value component" impl mentioned above, we get
many-to-many support "for free", as it would allow inserting multiple
copies of a Relationship component with different target entities.

Fixes #3742 (If this PR is merged, I think we should open more targeted
followup issues for the work above, with a fresh tracking issue free of
the large amount of less-directed historical context)
Fixes #17301
Fixes #12235 
Fixes #15299
Fixes #15308 

## Migration Guide

* Replace `ChildBuilder` with `ChildSpawnerCommands`.
* Replace calls to `.set_parent(parent_id)` with
`.insert(Parent(parent_id))`.
* Replace calls to `.replace_children()` with `.remove::<Children>()`
followed by `.add_children()`. Note that you'll need to manually despawn
any children that are not carried over.
* Replace calls to `.despawn_recursive()` with `.despawn()`.
* Replace calls to `.despawn_descendants()` with
`.despawn_related::<Children>()`.
* If you have any calls to `.despawn()` which depend on the children
being preserved, you'll need to remove the `Children` component first.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-01-18 22:20:30 +00:00

555 lines
19 KiB
Rust

#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
//! A UI-centric focus system for Bevy.
//!
//! This crate provides a system for managing input focus in Bevy applications, including:
//! * [`InputFocus`], a resource for tracking which entity has input focus.
//! * Methods for getting and setting input focus via [`InputFocus`] and [`IsFocusedHelper`].
//! * A generic [`FocusedInput`] event for input events which bubble up from the focused entity.
//! * Various navigation frameworks for moving input focus between entities based on user input, such as [`tab_navigation`] and [`directional_navigation`].
//!
//! This crate does *not* provide any integration with UI widgets: this is the responsibility of the widget crate,
//! which should depend on [`bevy_input_focus`](crate).
pub mod directional_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_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal};
use bevy_input::{gamepad::GamepadButtonChangedEvent, keyboard::KeyboardInput, mouse::MouseWheel};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{prelude::*, Reflect};
use bevy_window::{PrimaryWindow, Window};
use core::fmt::Debug;
/// Resource representing which entity has input focus, if any. Input events (other than pointer-like inputs) will be
/// dispatched to the current focus entity, or to the primary window if no entity has focus.
///
/// Changing the input focus is as easy as modifying this resource.
///
/// # Examples
///
/// From within a system:
///
/// ```rust
/// use bevy_ecs::prelude::*;
/// use bevy_input_focus::InputFocus;
///
/// fn clear_focus(mut input_focus: ResMut<InputFocus>) {
/// input_focus.clear();
/// }
/// ```
///
/// With exclusive (or deferred) world access:
///
/// ```rust
/// use bevy_ecs::prelude::*;
/// use bevy_input_focus::InputFocus;
///
/// fn set_focus_from_world(world: &mut World) {
/// let entity = world.spawn_empty().id();
///
/// // Fetch the resource from the world
/// let mut input_focus = world.resource_mut::<InputFocus>();
/// // Then mutate it!
/// input_focus.set(entity);
///
/// // Or you can just insert a fresh copy of the resource
/// // which will overwrite the existing one.
/// world.insert_resource(InputFocus::from_entity(entity));
/// }
/// ```
#[derive(Clone, Debug, Default, Resource)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Default, Resource)
)]
pub struct InputFocus(pub Option<Entity>);
impl InputFocus {
/// 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.
///
/// Note that this resource is not used by [`bevy_input_focus`](crate) itself, but is provided for
/// convenience to UI widgets or frameworks that want to display a focus indicator.
/// [`InputFocus`] may still be `Some` even if the focus indicator is not visible.
///
/// The value of this resource should be set by your focus navigation solution.
/// 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 clicks on a different element.
/// By contrast, a console-style UI intended to be navigated with a gamepad may always have the focus indicator visible.
///
/// To easily access information about whether focus indicators should be shown for a given entity, use the [`IsFocused`] trait.
///
/// By default, this resource is set to `false`.
#[derive(Clone, Debug, Resource, Default)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Resource))]
pub struct InputFocusVisible(pub bool);
/// A bubble-able user input event that starts at the currently focused entity.
///
/// This event is normally dispatched to the current input focus entity, if any.
/// If no entity has input focus, then the event is dispatched to the main window.
///
/// To set up your own bubbling input event, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
/// in the [`InputFocusSet::Dispatch`] system set during [`PreUpdate`].
#[derive(Clone, Debug, Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct FocusedInput<E: Event + Clone> {
/// The underlying input event.
pub input: E,
/// The primary window entity.
window: Entity,
}
impl<E: Event + Clone> Event for FocusedInput<E> {
type Traversal = WindowTraversal;
const AUTO_PROPAGATE: bool = true;
}
#[derive(QueryData)]
/// These are for accessing components defined on the targeted entity
pub struct WindowTraversal {
parent: Option<&'static Parent>,
window: Option<&'static Window>,
}
impl<E: Event + Clone> Traversal<FocusedInput<E>> for WindowTraversal {
fn traverse(item: Self::Item<'_>, event: &FocusedInput<E>) -> Option<Entity> {
let WindowTraversalItem { parent, window } = item;
// Send event to parent, if it has one.
if let Some(parent) = parent {
return Some(parent.get());
};
// Otherwise, send it to the window entity (unless this is a window entity).
if window.is_none() {
return Some(event.window);
}
None
}
}
/// Plugin which sets up systems for dispatching bubbling keyboard and gamepad button events to the focused entity.
///
/// 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;
impl Plugin for InputDispatchPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, set_initial_focus)
.init_resource::<InputFocus>()
.init_resource::<InputFocusVisible>()
.add_systems(
PreUpdate,
(
dispatch_focused_input::<KeyboardInput>,
dispatch_focused_input::<GamepadButtonChangedEvent>,
dispatch_focused_input::<MouseWheel>,
)
.in_set(InputFocusSet::Dispatch),
);
#[cfg(feature = "bevy_reflect")]
app.register_type::<AutoFocus>()
.register_type::<InputFocus>()
.register_type::<InputFocusVisible>();
}
}
/// System sets for [`bevy_input_focus`](crate).
///
/// These systems run in the [`PreUpdate`] schedule.
#[derive(SystemSet, Debug, PartialEq, Eq, Hash, Clone)]
pub enum InputFocusSet {
/// System which dispatches bubbled input events to the focused entity, or to the primary window.
Dispatch,
}
/// Sets the initial focus to the primary window, if any.
pub fn set_initial_focus(
mut input_focus: ResMut<InputFocus>,
window: Single<Entity, With<PrimaryWindow>>,
) {
input_focus.0 = Some(*window);
}
/// System which dispatches bubbled input events to the focused entity, or to the primary window
/// if no entity has focus.
pub fn dispatch_focused_input<E: Event + Clone>(
mut key_events: EventReader<E>,
focus: Res<InputFocus>,
windows: Query<Entity, With<PrimaryWindow>>,
mut commands: Commands,
) {
if let Ok(window) = windows.get_single() {
// If an element has keyboard focus, then dispatch the input event to that element.
if let Some(focused_entity) = focus.0 {
for ev in key_events.read() {
commands.trigger_targets(
FocusedInput {
input: ev.clone(),
window,
},
focused_entity,
);
}
} else {
// If no element has input focus, then dispatch the input event to the primary window.
// There should be only one primary window.
for ev in key_events.read() {
commands.trigger_targets(
FocusedInput {
input: ev.clone(),
window,
},
window,
);
}
}
}
}
/// Trait which defines methods to check if an entity currently has focus.
///
/// This is implemented for [`World`] and [`IsFocusedHelper`].
/// [`DeferredWorld`](bevy_ecs::world::DeferredWorld) indirectly implements it through [`Deref`].
///
/// For use within systems, use [`IsFocusedHelper`].
///
/// Modify the [`InputFocus`] resource to change the focused entity.
///
/// [`Deref`]: std::ops::Deref
pub trait IsFocused {
/// Returns true if the given entity has input focus.
fn is_focused(&self, entity: Entity) -> bool;
/// 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;
/// Returns true if the given entity has input focus and the focus indicator should be visible.
fn is_focus_visible(&self, entity: Entity) -> bool;
/// Returns true if the given entity, or any descendant, has input focus and the focus
/// indicator should be visible.
fn is_focus_within_visible(&self, entity: Entity) -> bool;
}
/// 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)]
pub struct IsFocusedHelper<'w, 's> {
parent_query: Query<'w, 's, &'static Parent>,
input_focus: Option<Res<'w, InputFocus>>,
input_focus_visible: Option<Res<'w, InputFocusVisible>>,
}
impl IsFocused for IsFocusedHelper<'_, '_> {
fn is_focused(&self, entity: Entity) -> bool {
self.input_focus
.as_deref()
.and_then(|f| f.0)
.is_some_and(|e| e == entity)
}
fn is_focus_within(&self, entity: Entity) -> bool {
let Some(focus) = self.input_focus.as_deref().and_then(|f| f.0) else {
return false;
};
if focus == entity {
return true;
}
self.parent_query.iter_ancestors(focus).any(|e| e == entity)
}
fn is_focus_visible(&self, entity: Entity) -> bool {
self.input_focus_visible.as_deref().is_some_and(|vis| vis.0) && self.is_focused(entity)
}
fn is_focus_within_visible(&self, entity: Entity) -> bool {
self.input_focus_visible.as_deref().is_some_and(|vis| vis.0) && self.is_focus_within(entity)
}
}
impl IsFocused for World {
fn is_focused(&self, entity: Entity) -> bool {
self.get_resource::<InputFocus>()
.and_then(|f| f.0)
.is_some_and(|f| f == entity)
}
fn is_focus_within(&self, entity: Entity) -> bool {
let Some(focus) = self.get_resource::<InputFocus>().and_then(|f| f.0) else {
return false;
};
let mut e = focus;
loop {
if e == entity {
return true;
}
if let Some(parent) = self.entity(e).get::<Parent>().map(Parent::get) {
e = parent;
} else {
return false;
}
}
}
fn is_focus_visible(&self, entity: Entity) -> bool {
self.get_resource::<InputFocusVisible>()
.is_some_and(|vis| vis.0)
&& self.is_focused(entity)
}
fn is_focus_within_visible(&self, entity: Entity) -> bool {
self.get_resource::<InputFocusVisible>()
.is_some_and(|vis| vis.0)
&& self.is_focus_within(entity)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bevy_ecs::{
component::ComponentId, observer::Trigger, system::RunSystemOnce, world::DeferredWorld,
};
use bevy_input::{
keyboard::{Key, KeyCode},
ButtonState, InputPlugin,
};
use bevy_window::WindowResolution;
use smol_str::SmolStr;
#[derive(Component)]
#[component(on_add = set_focus_on_add)]
struct SetFocusOnAdd;
fn set_focus_on_add(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
let mut input_focus = world.resource_mut::<InputFocus>();
input_focus.set(entity);
}
#[derive(Component, Default)]
struct GatherKeyboardEvents(String);
fn gather_keyboard_events(
trigger: Trigger<FocusedInput<KeyboardInput>>,
mut query: Query<&mut GatherKeyboardEvents>,
) {
if let Ok(mut gather) = query.get_mut(trigger.target()) {
if let Key::Character(c) = &trigger.input.logical_key {
gather.0.push_str(c.as_str());
}
}
}
const KEY_A_EVENT: KeyboardInput = KeyboardInput {
key_code: KeyCode::KeyA,
logical_key: Key::Character(SmolStr::new_static("A")),
state: ButtonState::Pressed,
text: Some(SmolStr::new_static("A")),
repeat: false,
window: Entity::PLACEHOLDER,
};
#[test]
fn test_no_panics_if_resource_missing() {
let mut app = App::new();
// Note that we do not insert InputFocus here!
let entity = app.world_mut().spawn_empty().id();
assert!(!app.world().is_focused(entity));
app.world_mut()
.run_system_once(move |helper: IsFocusedHelper| {
assert!(!helper.is_focused(entity));
assert!(!helper.is_focus_within(entity));
assert!(!helper.is_focus_visible(entity));
assert!(!helper.is_focus_within_visible(entity));
})
.unwrap();
app.world_mut()
.run_system_once(move |world: DeferredWorld| {
assert!(!world.is_focused(entity));
assert!(!world.is_focus_within(entity));
assert!(!world.is_focus_visible(entity));
assert!(!world.is_focus_within_visible(entity));
})
.unwrap();
}
#[test]
fn test_keyboard_events() {
fn get_gathered(app: &App, entity: Entity) -> &str {
app.world()
.entity(entity)
.get::<GatherKeyboardEvents>()
.unwrap()
.0
.as_str()
}
let mut app = App::new();
app.add_plugins((InputPlugin, InputDispatchPlugin))
.add_observer(gather_keyboard_events);
let window = Window {
resolution: WindowResolution::new(800., 600.),
..Default::default()
};
app.world_mut().spawn((window, PrimaryWindow));
// Run the world for a single frame to set up the initial focus
app.update();
let entity_a = app
.world_mut()
.spawn((GatherKeyboardEvents::default(), SetFocusOnAdd))
.id();
let child_of_b = app
.world_mut()
.spawn((GatherKeyboardEvents::default(),))
.id();
let entity_b = app
.world_mut()
.spawn((GatherKeyboardEvents::default(),))
.add_child(child_of_b)
.id();
assert!(app.world().is_focused(entity_a));
assert!(!app.world().is_focused(entity_b));
assert!(!app.world().is_focused(child_of_b));
assert!(!app.world().is_focus_visible(entity_a));
assert!(!app.world().is_focus_visible(entity_b));
assert!(!app.world().is_focus_visible(child_of_b));
// entity_a should receive this event
app.world_mut().send_event(KEY_A_EVENT);
app.update();
assert_eq!(get_gathered(&app, entity_a), "A");
assert_eq!(get_gathered(&app, entity_b), "");
assert_eq!(get_gathered(&app, child_of_b), "");
app.world_mut().insert_resource(InputFocus(None));
assert!(!app.world().is_focused(entity_a));
assert!(!app.world().is_focus_visible(entity_a));
// This event should be lost
app.world_mut().send_event(KEY_A_EVENT);
app.update();
assert_eq!(get_gathered(&app, entity_a), "A");
assert_eq!(get_gathered(&app, entity_b), "");
assert_eq!(get_gathered(&app, child_of_b), "");
app.world_mut()
.insert_resource(InputFocus::from_entity(entity_b));
assert!(app.world().is_focused(entity_b));
assert!(!app.world().is_focused(child_of_b));
app.world_mut()
.run_system_once(move |mut input_focus: ResMut<InputFocus>| {
input_focus.set(child_of_b);
})
.unwrap();
assert!(app.world().is_focus_within(entity_b));
// These events should be received by entity_b and child_of_b
app.world_mut().send_event_batch([KEY_A_EVENT; 4]);
app.update();
assert_eq!(get_gathered(&app, entity_a), "A");
assert_eq!(get_gathered(&app, entity_b), "AAAA");
assert_eq!(get_gathered(&app, child_of_b), "AAAA");
app.world_mut().resource_mut::<InputFocusVisible>().0 = true;
app.world_mut()
.run_system_once(move |helper: IsFocusedHelper| {
assert!(!helper.is_focused(entity_a));
assert!(!helper.is_focus_within(entity_a));
assert!(!helper.is_focus_visible(entity_a));
assert!(!helper.is_focus_within_visible(entity_a));
assert!(!helper.is_focused(entity_b));
assert!(helper.is_focus_within(entity_b));
assert!(!helper.is_focus_visible(entity_b));
assert!(helper.is_focus_within_visible(entity_b));
assert!(helper.is_focused(child_of_b));
assert!(helper.is_focus_within(child_of_b));
assert!(helper.is_focus_visible(child_of_b));
assert!(helper.is_focus_within_visible(child_of_b));
})
.unwrap();
app.world_mut()
.run_system_once(move |world: DeferredWorld| {
assert!(!world.is_focused(entity_a));
assert!(!world.is_focus_within(entity_a));
assert!(!world.is_focus_visible(entity_a));
assert!(!world.is_focus_within_visible(entity_a));
assert!(!world.is_focused(entity_b));
assert!(world.is_focus_within(entity_b));
assert!(!world.is_focus_visible(entity_b));
assert!(world.is_focus_within_visible(entity_b));
assert!(world.is_focused(child_of_b));
assert!(world.is_focus_within(child_of_b));
assert!(world.is_focus_visible(child_of_b));
assert!(world.is_focus_within_visible(child_of_b));
})
.unwrap();
}
}