Skip to content

Add FocusGained and FocusLost entity events#23723

Merged
alice-i-cecile merged 11 commits intobevyengine:mainfrom
alice-i-cecile:focus-events
Apr 10, 2026
Merged

Add FocusGained and FocusLost entity events#23723
alice-i-cecile merged 11 commits intobevyengine:mainfrom
alice-i-cecile:focus-events

Conversation

@alice-i-cecile
Copy link
Copy Markdown
Member

Objective

When working with more complex widgets, users have repeatedly asked for a way to reliably detect when an entity gains or loses focus. They have not, however, filed an issue about this 🎟️

These are called focus / focusin + blur / focusout on the web. The former element in these pairs does not bubble, while the latter does.

Because Bevy's observer infrastructure allows you to get the original target of a bubbled event, we can create only bubbling variations.

@viridia has suggested that #23707 would benefit from a form of this feature.

Rejected designs

The simplest approach would be to simply add a previously_focused field on InputFocus, and then emit events based on the difference using an ordinary system. Unfortunately, this has a serious flaw: events are lost if this changes state multiple times in the same frame.

We can resolve this problem by completely locking down access, and requiring commands or events to be used to change the input focus. This ensures no changes are lost and sends them off semi-immediately, but is a major breaking change to this API and prevents immediate-mode checks of "what is the current input focus".

Solution

We can do better. If we sacrifice "FocusGained and Lost must be emitted immediately", we can track changes inside of InputFocus, before sending them off in an ordinary system.

This is minimally breaking (you have to use the getters/setters now), and ensures no gained/lost events are ever missed. Users who completely overwrite InputFocus (e.g. by using from_entity) will miss changes, but frankly, you deserve it if you ignore the nice setters and clear warnings in the docs.

  • Define FocusGained and FocusLost events. Split to their own file for cleanliness.
  • Lock down access to InputFocus, forcing users to always go through the existing getters and setters.
  • Modify InputFocus to track changes as they have been made.
  • Add a system to drain these once-per-frame in PostUpdate, converting them into FocusGained and FocusLost.
    • This timing helps ensure that any user / library code that changes the focus has time to run, but rendering code that relies on accurate focus information to display widgets has the information available.
    • I could not find existing systems in PostUpdate that this needed a relative ordering for.
  • Create an InputFocusPlugin to store this new system, stealing some of the setup that was previously in InputDispatchPlugin. Somewhat incidentally, this fixes Dependency coupling between widgets and input focus #19057, by selecting option 1.

Testing

I was not very confident that my implementation of this logic was correct, so I wrote a rather aggressive set of mid-level tests, using App.

They pass, so apparently my first implementation was actually good enough.

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide X-Contentious There are nontrivial implications that should be thought through D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes labels Apr 8, 2026
@alice-i-cecile alice-i-cecile added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Apr 8, 2026
@github-project-automation github-project-automation bot moved this to Needs SME Triage in UI Apr 8, 2026
Comment thread _release-content/migration-guides/input_focus_setting_getting.md Outdated
@alice-i-cecile alice-i-cecile added this to the 0.19 milestone Apr 8, 2026
@alice-i-cecile
Copy link
Copy Markdown
Member Author

I'm adding this to the 0.19 milestone as I think it will be extremely useful for building UI widgets in earnest, but we can cut it if needed.

Copy link
Copy Markdown
Contributor

@viridia viridia left a comment

Choose a reason for hiding this comment

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

Perfect.

Copy link
Copy Markdown
Contributor

@Zeophlite Zeophlite left a comment

Choose a reason for hiding this comment

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

Very cool

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 9, 2026
@alice-i-cecile alice-i-cecile enabled auto-merge April 9, 2026 01:54
Comment thread crates/bevy_ui_widgets/src/menu.rs
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Apr 10, 2026
Merged via the queue into bevyengine:main with commit ffa4b0e Apr 10, 2026
38 checks passed
@github-project-automation github-project-automation bot moved this from Needs SME Triage to Done in UI Apr 10, 2026
viridia pushed a commit to viridia/bevy that referenced this pull request Apr 10, 2026
When working with more complex widgets, users have repeatedly asked for
a way to reliably detect when an entity gains or loses focus. They have
not, however, filed an issue about this 🎟️

These are called
[`focus`](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event)
/
[`focusin`](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event)
+
[`blur`](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event)
/
[`focusout`](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event)
on the web. The former element in these pairs does not bubble, while the
latter does.

Because Bevy's observer infrastructure allows you to get the original
target of a bubbled event, we can create only bubbling variations.

@viridia has suggested that
bevyengine#23707 would benefit from a form
of this feature.

The simplest approach would be to simply add a `previously_focused`
field on `InputFocus`, and then emit events based on the difference
using an ordinary system. Unfortunately, this has a serious flaw: events
are lost if this changes state multiple times in the same frame.

We can resolve this problem by completely locking down access, and
requiring commands or events to be used to change the input focus. This
ensures no changes are lost and sends them off semi-immediately, but is
a major breaking change to this API and prevents immediate-mode checks
of "what is the current input focus".

We can do better. If we sacrifice "FocusGained and Lost must be emitted
immediately", we can track changes inside of `InputFocus`, before
sending them off in an ordinary system.

This is minimally breaking (you have to use the getters/setters now),
and ensures no gained/lost events are ever missed. Users who completely
overwrite `InputFocus` (e.g. by using `from_entity`) will miss changes,
but frankly, you deserve it if you ignore the nice setters and clear
warnings in the docs.

- Define `FocusGained` and `FocusLost` events. Split to their own file
for cleanliness.
- Lock down access to `InputFocus`, forcing users to always go through
the existing getters and setters.
- Modify `InputFocus` to track changes as they have been made.
- Add a system to drain these once-per-frame in `PostUpdate`, converting
them into `FocusGained` and `FocusLost`.
- This timing helps ensure that any user / library code that changes the
focus has time to run, but rendering code that relies on accurate focus
information to display widgets has the information available.
- I could not find existing systems in `PostUpdate` that this needed a
relative ordering for.
- Create an `InputFocusPlugin` to store this new system, stealing some
of the setup that was previously in `InputDispatchPlugin`. Somewhat
incidentally, this fixes bevyengine#19057, by selecting option 1.

I was *not* very confident that my implementation of this logic was
correct, so I wrote a rather aggressive set of mid-level tests, using
`App`.

They pass, so apparently my first implementation was actually good
enough.
mate-h pushed a commit to mate-h/bevy that referenced this pull request Apr 14, 2026
# Objective

When working with more complex widgets, users have repeatedly asked for
a way to reliably detect when an entity gains or loses focus. They have
not, however, filed an issue about this 🎟️

These are called
[`focus`](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event)
/
[`focusin`](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event)
+
[`blur`](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event)
/
[`focusout`](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event)
on the web. The former element in these pairs does not bubble, while the
latter does.

Because Bevy's observer infrastructure allows you to get the original
target of a bubbled event, we can create only bubbling variations.

@viridia has suggested that
bevyengine#23707 would benefit from a form
of this feature.

## Rejected designs

The simplest approach would be to simply add a `previously_focused`
field on `InputFocus`, and then emit events based on the difference
using an ordinary system. Unfortunately, this has a serious flaw: events
are lost if this changes state multiple times in the same frame.

We can resolve this problem by completely locking down access, and
requiring commands or events to be used to change the input focus. This
ensures no changes are lost and sends them off semi-immediately, but is
a major breaking change to this API and prevents immediate-mode checks
of "what is the current input focus".

## Solution

We can do better. If we sacrifice "FocusGained and Lost must be emitted
immediately", we can track changes inside of `InputFocus`, before
sending them off in an ordinary system.

This is minimally breaking (you have to use the getters/setters now),
and ensures no gained/lost events are ever missed. Users who completely
overwrite `InputFocus` (e.g. by using `from_entity`) will miss changes,
but frankly, you deserve it if you ignore the nice setters and clear
warnings in the docs.

- Define `FocusGained` and `FocusLost` events. Split to their own file
for cleanliness.
- Lock down access to `InputFocus`, forcing users to always go through
the existing getters and setters.
- Modify `InputFocus` to track changes as they have been made.
- Add a system to drain these once-per-frame in `PostUpdate`, converting
them into `FocusGained` and `FocusLost`.
- This timing helps ensure that any user / library code that changes the
focus has time to run, but rendering code that relies on accurate focus
information to display widgets has the information available.
- I could not find existing systems in `PostUpdate` that this needed a
relative ordering for.
- Create an `InputFocusPlugin` to store this new system, stealing some
of the setup that was previously in `InputDispatchPlugin`. Somewhat
incidentally, this fixes bevyengine#19057, by selecting option 1.

## Testing

I was *not* very confident that my implementation of this logic was
correct, so I wrote a rather aggressive set of mid-level tests, using
`App`.

They pass, so apparently my first implementation was actually good
enough.
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-Feature A new feature, making something new possible D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Contentious There are nontrivial implications that should be thought through

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Dependency coupling between widgets and input focus

4 participants