Skip to content

Commit

Permalink
Remove SetInputFocus helper trait (#16888)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
alice-i-cecile authored Dec 19, 2024
1 parent 753d46f commit df7aa44
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 85 deletions.
8 changes: 5 additions & 3 deletions crates/bevy_input_focus/src/autofocus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
}
}
133 changes: 51 additions & 82 deletions crates/bevy_input_focus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -23,16 +23,49 @@ 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};
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>);

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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},
Expand All @@ -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)]
Expand All @@ -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()
Expand Down Expand Up @@ -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));
Expand All @@ -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));
Expand Down

0 comments on commit df7aa44

Please sign in to comment.