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:
parent
753d46fd95
commit
df7aa445e9
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user