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 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,
|
/// This can be useful for things like dialog boxes, the first text input in a form,
|
||||||
/// or the first button in a game menu.
|
/// or the first button in a game menu.
|
||||||
@ -16,5 +16,7 @@ use crate::SetInputFocus;
|
|||||||
pub struct AutoFocus;
|
pub struct AutoFocus;
|
||||||
|
|
||||||
fn on_auto_focus_added(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
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:
|
//! This crate provides a system for managing input focus in Bevy applications, including:
|
||||||
//! * [`InputFocus`], 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 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.
|
//! * 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,
|
//! 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::*;
|
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};
|
||||||
prelude::*, query::QueryData, system::SystemParam, traversal::Traversal, world::DeferredWorld,
|
|
||||||
};
|
|
||||||
use bevy_hierarchy::{HierarchyQueryExt, Parent};
|
use bevy_hierarchy::{HierarchyQueryExt, Parent};
|
||||||
use bevy_input::{gamepad::GamepadButtonChangedEvent, keyboard::KeyboardInput, mouse::MouseWheel};
|
use bevy_input::{gamepad::GamepadButtonChangedEvent, keyboard::KeyboardInput, mouse::MouseWheel};
|
||||||
use bevy_window::{PrimaryWindow, Window};
|
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
|
/// 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.
|
||||||
|
///
|
||||||
|
/// 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)]
|
#[derive(Clone, Debug, Default, Resource)]
|
||||||
pub struct InputFocus(pub Option<Entity>);
|
pub struct InputFocus(pub Option<Entity>);
|
||||||
|
|
||||||
@ -75,74 +108,6 @@ impl InputFocus {
|
|||||||
#[derive(Clone, Debug, Resource)]
|
#[derive(Clone, Debug, Resource)]
|
||||||
pub struct InputFocusVisible(pub bool);
|
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.
|
/// 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.
|
/// 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.
|
/// Trait which defines methods to check if an entity currently has focus.
|
||||||
///
|
///
|
||||||
/// This is implemented for [`World`] and [`IsFocusedHelper`].
|
/// 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`].
|
/// 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
|
/// [`Deref`]: std::ops::Deref
|
||||||
pub trait IsFocused {
|
pub trait IsFocused {
|
||||||
@ -370,7 +335,9 @@ impl IsFocused for World {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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_hierarchy::BuildChildren;
|
||||||
use bevy_input::{
|
use bevy_input::{
|
||||||
keyboard::{Key, KeyCode},
|
keyboard::{Key, KeyCode},
|
||||||
@ -384,7 +351,8 @@ mod tests {
|
|||||||
struct SetFocusOnAdd;
|
struct SetFocusOnAdd;
|
||||||
|
|
||||||
fn set_focus_on_add(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
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)]
|
#[derive(Component, Default)]
|
||||||
@ -411,12 +379,12 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_without_plugin() {
|
fn test_no_panics_if_resource_missing() {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
// Note that we do not insert InputFocus here!
|
||||||
|
|
||||||
let entity = app.world_mut().spawn_empty().id();
|
let entity = app.world_mut().spawn_empty().id();
|
||||||
|
|
||||||
app.world_mut().set_input_focus(entity);
|
|
||||||
assert!(!app.world().is_focused(entity));
|
assert!(!app.world().is_focused(entity));
|
||||||
|
|
||||||
app.world_mut()
|
app.world_mut()
|
||||||
@ -494,7 +462,7 @@ mod tests {
|
|||||||
assert_eq!(get_gathered(&app, entity_b), "");
|
assert_eq!(get_gathered(&app, entity_b), "");
|
||||||
assert_eq!(get_gathered(&app, child_of_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_focused(entity_a));
|
||||||
assert!(!app.world().is_focus_visible(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, entity_b), "");
|
||||||
assert_eq!(get_gathered(&app, child_of_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(entity_b));
|
||||||
assert!(!app.world().is_focused(child_of_b));
|
assert!(!app.world().is_focused(child_of_b));
|
||||||
|
|
||||||
app.world_mut()
|
app.world_mut()
|
||||||
.run_system_once(move |mut commands: Commands| {
|
.run_system_once(move |mut input_focus: ResMut<InputFocus>| {
|
||||||
commands.set_input_focus(child_of_b);
|
input_focus.set(child_of_b);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(app.world().is_focus_within(entity_b));
|
assert!(app.world().is_focus_within(entity_b));
|
||||||
|
Loading…
Reference in New Issue
Block a user