Add "bevy_input_focus" crate.
Objective
Define a framework for handling keyboard focus and bubbled keyboard events, as discussed in #15374.
Solution
Introduces a new crate, bevy_input_focus. This crate provides:
- A resource for tracking which entity has keyboard focus.
- Methods for getting and setting keyboard focus.
- Event definitions for triggering bubble-able keyboard input events to the focused entity.
- A system for dispatching keyboard input events to the focused entity.
This crate does not provide any integration with UI widgets, or provide functions for tab navigation or gamepad-based focus navigation, as those are typically application-specific.
Testing
Most of the code has been copied from a different project, one that has been well tested. However, most of what's in this module consists of type definitions, with relatively small amounts of executable code. That being said, I expect that there will be substantial bikeshedding on the design, and I would prefer to hold off writing tests until after things have settled.
I think that an example would be appropriate, however I'm waiting on a few other pending changes to Bevy before doing so. In particular, I can see a simple example with four buttons, with focus navigation between them, and which can be triggered by the keyboard.
@alice-i-cecile
I really want this, and I like the fundamental design. At the start of the 0.16 cycle I'm going to come back to this, evaluate scope and then decide how to proceed. We may want a working group for all of the follow-up.
One thing that has not been established is the relationship between this and bevy_a11y. I had originally assumed that this would depend on bevy_a11y, but it sounds like you were thinking that it would be the other way around.
Yeah, my expectation is that bevy_a11y is a consumer of the focus data produced here from user input, and so should depend on this crate.
I've got a library waiting in the wings that needs keyboard focus before moving forward. I'll be happy to attempt integrating this as a means to exercise and review it. Will wait till Alice has had her say.
There are two immediate follow-ups that need to happen:
- Integration with bevy_a11y
- @NthTensor's proposed change to allow bubbled events to propagate to the window. This is important because it allows "global" key handlers to work regardless of whether focus is Some or None:
- If focus is None, keyboard events are dispatched to the window directly
- If focus is Some, keyboard events are first processed by the focus widget and its ancestors, and if none of them call propagate(false) then the window gets the event.
I'll have time to publish that work tomorrow.
I've updated this so that if the current KeyboardFocus entity is None, then keyboard events are dispatched to the PrimaryWindow instead of all windows. If there's no primary window, then the event is dropped.
One open issue is where this crate belongs in the dependency order. Right now it's last in the list, but the plan is for bevy_a11y to depend on it, so it will likely need to come before that. (The actual dependency will be added in a follow-up PR.)
@cart and I have approved this approach in principle here, so we should nail down the preliminary details via the standard review process and get this merged.
I've updated the PR as follows:
- Rebased to latest Bevy main
- Re-ordered the new crate to come after both
bevy_inputandbevy_a11y, but not at the end of the list. - Added
KeyboardFocusVisibleresource based on our discussion on Discord. - Added trait extensions for
WorldandDeferredWorldfor testing whether an element, or its descendants, have keyboard focus. - Added additional documentation.
You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.
Does this still need bubble-to-window? I have an open PR if so.
Does this still need bubble-to-window? I have an open PR if so.
Yes, this is assuming that bubble-to-window is available.
@NthTensor Consider the following scenario: Let's say we have a blender-like app which has a lot of hotkeys. Say the user presses 'q' for 'quit'. There are three cases:
- No widget has focus (focus = None) - the keyboard event gets sent directly to the primary window, where it is handled and processed through the global hotkey map.
- A button has focus - the keyboard event is dispatched to the button entity, but since button doesn't recognize 'q' as a shortcut, it bubbles all the way to the top, eventually reaching the window, where it has handled as in the previous case.
- A text input field has focus - in this case, the key is sent to the text input entity, which does recognize 'q' - so it calls
propagate(false), and inserts the letter 'q' into the text buffer. The keyboard even never reaches the window.
The code in this PR handles the 'no-focus' case by detecting focus = None and sending the event directly to the primary window. But I still need your PR to handle the second case, where the event is sent to the focus entity, but bubbles to the top of the hierarchy.
I was thinking in local multiplayer with the impression that it might need something more complex. But I think this PR design is also OK for local multiplayer. These are game specific considerations:
- in the menus, the game can decide that the focus can be changed by any player (input device)… or it could be locked to "Player 1" (or whoever opened the pause menu)
- a character selection screen is purely game code
@doup
I was thinking in local multiplayer with the impression that it might need something more complex. But I think this PR design is also OK for local multiplayer.
I deliberately decided to punt on the issue of multiple game controllers. First, I don't know if there are any games which use multiple controllers which also have the concept of "navigable focus". Second, even if there were such games, I'm not sure that they would conform to our expectations - that is, we could easily end up building something that didn't match the needs of those games. So it seems like the best solution is for games like that to build their own solutions.
I think gamepads are important for this crate (but not this PR). I only want to support a single gamepad though until we have a very clear need for multiple.
You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.
@viridia, imo the approach of this PR is correct. In https://github.com/bevyengine/bevy/pull/15611#issuecomment-2511020639 I meant that multi-focus is NOT something Bevy should solve.
I checked Super Mario Wonder which has local multiplayer:
- Player1 controls the "focus" in general menus
- While playing if any of the players pauses the game they have control of the "focus" on the pause menu.
- The only instance where multiple focus is used in the player select screen. This is game code, not Bevy's responsibility.
I don't have any other local multiplayer game, but I suspect is similar in any another game. Although, it would be nice if someone could check this in a fighting game or in Mario Kart. :-)
@viridia ping me when CI is green and I'll give this a final review before merging.
You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.
@mockersf One of the reasons why the CI is failing is because I wrote the dependencies as "0.15.0", however Bevy mainline in still has its version numbers as "0.15.0-dev".
I think main always uses the -dev suffix. Should probably be bumped to 0.16 now.
@alice-i-cecile I fixed the problem with the deps. I don't know what the other CI failures are, or whether they have anything to do with this PR.
All prs that touch the Cargo.toml are currently failing on that check-ban. It's flagging a newly unmaintained crate deep in our dependency tree (which incidentally happens to owned by a colleague of mine). Don't think we can fix it unless the crate we depend on updates. Maybe there's a way to ignore it?
Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to https://github.com/bevyengine/bevy-website/issues/1965 if you'd like to help out.