Remove SetInputFocus helper trait (#16888)

# Objective

The `SetInputFocus` trait is not very useful: we're just setting a
resource's value.

This is a very common and simple pattern, so we should expose it
directly to users rather than creating confusing indirection.

## Solution

Remove the `SetInputFocus` trait and migrate existing uses to just
modify the `InputFocus` resource. The helper methods on that type make
this nicer than before :)

P.S. This is non-breaking as bevy_input_focus has not yet shipped.

## Testing

Code compiles! CI will check the existing unit tests.
This commit is contained in:
Alice Cecile 2024-12-18 16:40:10 -08:00 committed by GitHub
parent 753d46fd95
commit df7aa445e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 56 additions and 85 deletions

View File

@ -2,9 +2,9 @@
use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld};
use crate::SetInputFocus;
use crate::InputFocus;
/// Indicates that this widget should automatically receive [`InputFocus`](crate::InputFocus).
/// Indicates that this widget should automatically receive [`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.
@ -16,5 +16,7 @@ use crate::SetInputFocus;
pub struct AutoFocus;
fn on_auto_focus_added(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
world.set_input_focus(entity);
if let Some(mut input_focus) = world.get_resource_mut::<InputFocus>() {
input_focus.set(entity);
}
}

View File

@ -9,7 +9,7 @@
//!
//! 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 [`SetInputFocus`], [`InputFocus`] and [`IsFocusedHelper`].
//! * 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.
//!
//! This crate does *not* provide any integration with UI widgets: this is the responsibility of the widget crate,
@ -23,9 +23,7 @@ mod autofocus;
pub use autofocus::*;
use bevy_app::{App, Plugin, PreUpdate, Startup};
use bevy_ecs::{
prelude::*, query::QueryData, system::SystemParam, traversal::Traversal, world::DeferredWorld,
};
use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal};
use bevy_hierarchy::{HierarchyQueryExt, Parent};
use bevy_input::{gamepad::GamepadButtonChangedEvent, keyboard::KeyboardInput, mouse::MouseWheel};
use bevy_window::{PrimaryWindow, Window};
@ -33,6 +31,41 @@ use core::fmt::Debug;
/// 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.
///
/// 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)]
pub struct InputFocus(pub Option<Entity>);
@ -75,74 +108,6 @@ impl InputFocus {
#[derive(Clone, Debug, Resource)]
pub struct InputFocusVisible(pub bool);
/// Helper functions for [`World`], [`DeferredWorld`] and [`Commands`] to set and clear input focus.
///
/// These methods are equivalent to modifying the [`InputFocus`] resource directly,
/// but only take effect when commands are applied.
///
/// See [`IsFocused`] for methods to check if an entity has focus.
pub trait SetInputFocus {
/// Set input focus to the given entity.
///
/// This is equivalent to setting the [`InputFocus`]'s entity to `Some(entity)`.
fn set_input_focus(&mut self, entity: Entity);
/// Clear input focus.
///
/// This is equivalent to setting the [`InputFocus`]'s entity to `None`.
fn clear_input_focus(&mut self);
}
impl SetInputFocus for World {
fn set_input_focus(&mut self, entity: Entity) {
if let Some(mut focus) = self.get_resource_mut::<InputFocus>() {
focus.0 = Some(entity);
}
}
fn clear_input_focus(&mut self) {
if let Some(mut focus) = self.get_resource_mut::<InputFocus>() {
focus.0 = None;
}
}
}
impl<'w> SetInputFocus for DeferredWorld<'w> {
fn set_input_focus(&mut self, entity: Entity) {
if let Some(mut focus) = self.get_resource_mut::<InputFocus>() {
focus.0 = Some(entity);
}
}
fn clear_input_focus(&mut self) {
if let Some(mut focus) = self.get_resource_mut::<InputFocus>() {
focus.0 = None;
}
}
}
/// Command to set input focus to the given entity.
///
/// Generated via the methods in [`SetInputFocus`].
pub struct SetFocusCommand(Option<Entity>);
impl Command for SetFocusCommand {
fn apply(self, world: &mut World) {
if let Some(mut focus) = world.get_resource_mut::<InputFocus>() {
focus.0 = self.0;
}
}
}
impl SetInputFocus for Commands<'_, '_> {
fn set_input_focus(&mut self, entity: Entity) {
self.queue(SetFocusCommand(Some(entity)));
}
fn clear_input_focus(&mut self) {
self.queue(SetFocusCommand(None));
}
}
/// 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.
@ -268,11 +233,11 @@ pub fn dispatch_focused_input<E: Event + Clone>(
/// Trait which defines methods to check if an entity currently has focus.
///
/// This is implemented for [`World`] and [`IsFocusedHelper`].
/// [`DeferredWorld`] indirectly implements it through [`Deref`].
/// [`DeferredWorld`](bevy_ecs::world::DeferredWorld) indirectly implements it through [`Deref`].
///
/// For use within systems, use [`IsFocusedHelper`].
///
/// See [`SetInputFocus`] for methods to set and clear input focus.
/// Modify the [`InputFocus`] resource to change the focused entity.
///
/// [`Deref`]: std::ops::Deref
pub trait IsFocused {
@ -370,7 +335,9 @@ impl IsFocused for World {
mod tests {
use super::*;
use bevy_ecs::{component::ComponentId, observer::Trigger, system::RunSystemOnce};
use bevy_ecs::{
component::ComponentId, observer::Trigger, system::RunSystemOnce, world::DeferredWorld,
};
use bevy_hierarchy::BuildChildren;
use bevy_input::{
keyboard::{Key, KeyCode},
@ -384,7 +351,8 @@ mod tests {
struct SetFocusOnAdd;
fn set_focus_on_add(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
world.set_input_focus(entity);
let mut input_focus = world.resource_mut::<InputFocus>();
input_focus.set(entity);
}
#[derive(Component, Default)]
@ -411,12 +379,12 @@ mod tests {
};
#[test]
fn test_without_plugin() {
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();
app.world_mut().set_input_focus(entity);
assert!(!app.world().is_focused(entity));
app.world_mut()
@ -494,7 +462,7 @@ mod tests {
assert_eq!(get_gathered(&app, entity_b), "");
assert_eq!(get_gathered(&app, child_of_b), "");
app.world_mut().clear_input_focus();
app.world_mut().insert_resource(InputFocus(None));
assert!(!app.world().is_focused(entity_a));
assert!(!app.world().is_focus_visible(entity_a));
@ -507,13 +475,14 @@ mod tests {
assert_eq!(get_gathered(&app, entity_b), "");
assert_eq!(get_gathered(&app, child_of_b), "");
app.world_mut().set_input_focus(entity_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 commands: Commands| {
commands.set_input_focus(child_of_b);
.run_system_once(move |mut input_focus: ResMut<InputFocus>| {
input_focus.set(child_of_b);
})
.unwrap();
assert!(app.world().is_focus_within(entity_b));