diff --git a/crates/bevy_input_focus/src/autofocus.rs b/crates/bevy_input_focus/src/autofocus.rs index ce4635ef4c6e1..10dea5f892461 100644 --- a/crates/bevy_input_focus/src/autofocus.rs +++ b/crates/bevy_input_focus/src/autofocus.rs @@ -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::() { + input_focus.set(entity); + } } diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 23ff6a967b657..4b0ec5a763c6f 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -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) { +/// 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::(); +/// // 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); @@ -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::() { - focus.0 = Some(entity); - } - } - - fn clear_input_focus(&mut self) { - if let Some(mut focus) = self.get_resource_mut::() { - 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::() { - focus.0 = Some(entity); - } - } - - fn clear_input_focus(&mut self) { - if let Some(mut focus) = self.get_resource_mut::() { - focus.0 = None; - } - } -} - -/// Command to set input focus to the given entity. -/// -/// Generated via the methods in [`SetInputFocus`]. -pub struct SetFocusCommand(Option); - -impl Command for SetFocusCommand { - fn apply(self, world: &mut World) { - if let Some(mut focus) = world.get_resource_mut::() { - 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( /// 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::(); + 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| { + input_focus.set(child_of_b); }) .unwrap(); assert!(app.world().is_focus_within(entity_b));