Skip to content

New simplified "click to focus" logic for core widgets. #19736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

viridia
Copy link
Contributor

@viridia viridia commented Jun 19, 2025

Click to focus is now a global observer.

Objective

Previously, the "click to focus" behavior was implemented in each individual headless widget, producing redundant logic.

Solution

The new scheme is to have a global observer which looks for pointer down events and triggers an AcquireFocus event on the target. This event bubbles until it finds an entity with TabIndex, and then focuses it.

Testing

Tested the changes using the various examples that have focusable widgets. (This will become easier to test when I add focus ring support to the examples, but that's for another day. For now you just have to know which keys to press.)

Migration

This change is backwards-compatible. People who want the new behavior will need to install the new plugin.

Click to focus is now a global observer.
@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-UI Graphical user interfaces, styles, layouts, and widgets C-Code-Quality A section of code that is hard to understand or change C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward and removed C-Bug An unexpected or incorrect behavior labels Jun 19, 2025
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this design, but two non-blocking comments:

  1. I think we should roll all of those supporting plugins into DefaultPlugins. Perhaps not for 0.17, but once it's ready. Users who don't want this will want to disable this code via features anyways, and can also opt-out by disabling plugins from there.
  2. We should consider making this the single, blessed path to set input focus. I don't see a clear argument for continuing to support other methods, and it's less confusing and buggy to only have one path.

@viridia
Copy link
Contributor Author

viridia commented Jun 19, 2025

  1. I think we should roll all of those supporting plugins into DefaultPlugins. Perhaps not for 0.17, but once it's ready. Users who don't want this will want to disable this code via features anyways, and can also opt-out by disabling plugins from there.

One thing to note is that "click to focus" currently only works with TabIndex, which means that it works with TabNavigation and does not work with DirectionalNavigation. The presumption is that DirectionalNavigation is used on platforms which don't have mice.

If we wanted to make this universal, we would need to have a Focusable marker which would work for both navigation systems.

@viridia
Copy link
Contributor Author

viridia commented Jun 19, 2025

That being said, I suppose we could merge the ClickToFocusPlugin with the TabNavigationPlugin and save a step.

@viridia
Copy link
Contributor Author

viridia commented Jun 19, 2025

Also, another open question: should I use WindowTraversal rather than ChildOf? If AcquireFocus reaches the window without hitting anything, should I set focus to None?

@alice-i-cecile
Copy link
Member

Also, another open question: should I use WindowTraversal rather than ChildOf? If AcquireFocus reaches the window without hitting anything, should I set focus to None?

Yeah, I like that idea.

@alice-i-cecile alice-i-cecile requested a review from MalekiRe June 19, 2025 21:11
@@ -207,43 +206,18 @@ pub(crate) fn slider_on_pointer_down(
)>,
q_thumb: Query<&ComputedNode, With<CoreSliderThumb>>,
q_children: Query<&Children>,
q_parents: Query<&ChildOf>,
focus: Option<ResMut<InputFocus>>,
focus_visible: Option<ResMut<InputFocusVisible>>,
mut commands: Commands,
ui_scale: Res<UiScale>,
) {
if q_thumb.contains(trigger.target()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no mechanism to block click propagation without an observer?

focus.0 = Some(ev.target());
}
} else if windows.contains(ev.target()) {
// Stop and focus it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Stop and focus it
// Stop and remove current focus

ev.propagate(false);
// Don't mutate unless we need to, for change detection
if focus.0.is_some() {
focus.0 = None;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
focus.0 = None;
focus.clear();

Comment on lines +377 to +381
if let Ok(window) = windows.single() {
commands
.entity(ev.target())
.trigger(AcquireFocus { window });
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by why the PrimaryWindow is returned if a focusable target isn't found, what if the clicked target isn't on the primary window?

Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from a couple of questions, looks good. Bubbling AquireFocus seems like the natural approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Code-Quality A section of code that is hard to understand or change C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants