bevyengine / bevyengine/bevy

Click-to-focus doesn't work with directional navigation

Open
#25,596 2 comments 0 reactions 0 assignees View on GitHub
A-UI C-Bug D-Modest M-Migration-Guide S-Ready-For-Implementation X-Uncontroversial
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

The `acquire_focus_tab_index` observer which is installed by `PointerFocusPlugin` in `bevy_input_focus` only works with `TabIndex`, not other kinds of focusable entities. This means that when you click on a widget that uses directional navigation, it immediately loses focus; worse, the focus is set to `None`, so you can't get back to it by navigating, since the directional system doesn't know where to start.

The problem is that `TabIndex` was originally modeled after the HTML `tab-index` attribute, which has two different jobs: it indicates where the element should be in the tab order, but it also indicates that the _element is focusable at all_. Unfortunately, Bevy has two different focus navigation systems, and only one of them uses `TabIndex`.

Worse, there's no single component in the directional nav framework that indicates "this widget is focusable" - we have `AutoDirectionalNavigation` and we have the directional navigation override, but both of these indicate _where the focus moves_ to when leaving the widget, not whether the widget can accept focus. Also, `AutoDirectionalNavigation` is in `bevy_ui`, which means that it's at a higher layer than `bevy_input_focus`, which cannot depend on it.

The right answer, I think, is to split `TabIndex` into two separate components: `Focusable`, which for now means that an entity can accept focus, and `TabIndex` which now only specifies the tab order.

We can make `Focusable` a required component for both `TabIndex` and `AutoDirectionalNavigation`, avoiding the need to migrate all the examples. For the manual directional navigation examples we might need to add `Focusable` explicitly.

We can update the sequential navigator to consider an entity with only `Focusable` (no `TabIndex`) to have an implicit tab index of zero. The acquire focus observer would then only use `Focusable`. All of the bevy feathers widgets which use `TabIndex::default()` would migration to just using `Focusable`.

We can also think about adding `Focusable` to all the headless widgets; we couldn't do this before, because we didn't want to assume that the widgets were only meant to be used in a sequential navigation context, but `Focusable` can work with either navigation type.

Another benefit of this approach: we can make a widget temporarily non-focusable by removing the `Focusable` marker, *without* losing it's tab order, which we couldn't before.

@alice-i-cecile @kfc35 @ickshonpe @gagnus

Contributor guide

Open the contributing guide

Research direction

Start in bevy_input_focus at acquire_focus_tab_index and PointerFocusPlugin, then trace TabIndex through the sequential navigator and AutoDirectionalNavigation in bevy_ui. Check the directional-navigation examples and bevy_feathers widgets to identify required migrations. Done means click focus is retained, Focusable drives focus acquisition, and sequential navigation handles Focusable without TabIndex.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
game-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.