Skip to content

TextInput - #24929

Open
viridia wants to merge 49 commits into
bevyengine:mainfrom
viridia:text_input
Open

TextInput#24929
viridia wants to merge 49 commits into
bevyengine:mainfrom
viridia:text_input

Conversation

@viridia

@viridia viridia commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This PR splits the functionality of the EditableText component into two separate components: EditableText (which lives in bevy_text) and TextInput (which lives in bevy_ui_widgets).

See release notes for more details.

@viridia
viridia requested a review from ickshonpe July 9, 2026 16:20
Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated

@ickshonpe ickshonpe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems fine, agree with the overall direction. I think maybe instead of the bool param on queue_edit, we could implement an is_destructive method for TextEdit that returns true for destructive TextEdit s.

Also I haven't thought it through properly but possibly there could be problems with IME if the widget is changed to readonly during composition? I'm not sure we should worry too much though.

Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
Comment thread crates/bevy_ui_widgets/src/text_input.rs
return;
};

if *text_input == TextInput::DisplayOnly {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if *text_input == TextInput::DisplayOnly {
if *text_input != TextInput::Editable {

Comment thread crates/bevy_ui_widgets/src/text_input.rs
Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
Comment on lines 410 to 413
@@ -360,7 +413,7 @@ fn update_ime_position(
/// IME is enabled when an `EditableText` gains focus and disabled when focus moves elsewhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Needs to be updated to add that IME state also depends on TextInput now.

Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
mut editable_text_query: Query<&mut EditableText, With<TextInput>>,
) {
if let Ok(mut editable_text) = editable_text_query.get_mut(trigger.entity) {
editable_text.queue_edit(TextEdit::clear_ime_compose());

@ickshonpe ickshonpe Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We also need to queue TextEdit::clear_ime_compose() somewhere when TextInput changes away from Editable.

Edit: Added it to the suggested changes for listen_for_ime_input_when_text_input_focused.

Comment thread crates/bevy_ui_widgets/src/text_input.rs Outdated
/// Cursor movement, selection, and copy to clipboard is still enabled, but no mutations are allowed
ReadOnly,
/// Display only, all interactions disabled - this is used by number input widget when dragging.
DisplayOnly,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Then this could be NonInteractable or something.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I also thought about Static

viridia and others added 3 commits July 11, 2026 09:26
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
@viridia

viridia commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

@ickshonpe @alice-i-cecile So we have a couple of outstanding issues here.

First, there's a system ambiguity which is causing the CI to fail, and which I am unsure how to fix.

Second, there's some unfinished bikeshedding around the naming and role of the TextInput component.

As I see it, we have two choices about how we can structure this:

Choice 1: EditableText is the primary component: it's a widget in it's own right (meaning, there are a bunch of observers associated with it), and the read/write mode is an auxiliary, possibly optional component. If we choose this route, then it makes sense to rename TextInput to something like TextEditable or ReadWriteMode and make it optional.

The biggest problem here is what to do about accessibility: we need to add AccessibilityNode as a required component if we are going to be consistent with the other widgets, and the only two candidates for this are EditableText and TextInput. I don't like the idea of adding a dep from bevy_text to bevy_a11y, since a11y is really about interaction, and is outside of bevy_text's wheelhouse. The only other option is to add the requirement dynamically in the plugin rather than using a Rust annotation / derive macro.

Choice 2: EditableText represents just a buffer, a passive component with no associated event listeners but lots of methods for manipulating text via queued actions; the actual "widget" is TextInput, and it, along with all of its observers, live in bevy_ui_text.

Finally, I want to mention that I need to solve this problem to unblock my work on FeathersNumberInput and the color picker dropdown generally.

@alice-i-cecile

Copy link
Copy Markdown
Member

Responded in #24929 (comment) :) Let me know if you can't work out the resolution of the ambiguity and I'll take a look.

@alice-i-cecile alice-i-cecile added A-UI Graphical user interfaces, styles, layouts, and widgets C-Code-Quality A section of code that is hard to understand or change 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 S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Jul 12, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in UI Jul 12, 2026
kfc35 and others added 2 commits July 21, 2026 13:37
…25104)

# Objective

- Part of bevyengine#24112 

## Solution

- Very straightforward port
- The only thing that is slightly new is adding `Hovered` to the
`FeathersRadio` helper entity to support some of this example’s
functionality re: blocking drag when over a radio button.

## Testing

- `cargo run --example mirror --features=“bevy_feathers”` should work
the same. Strangely this functions _better_ than main for me when moving
the fox.

---

## Showcase

<img width="1286" height="753" alt="Screenshot 2026-07-21 at 2 23 54 PM"
src="https://github.com/user-attachments/assets/dbca9793-a966-4c9f-91fa-b57e471da998"
/>
@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jul 22, 2026
…WriteMode::Readonly`.

This will be revisited after we refactor/rethink `InteractionDisabled`.
@viridia

viridia commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Looks like my latest change broke CI, not sure what caused it.

Oh, wait, I think I know.

@ickshonpe

ickshonpe commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@ickshonpe @alice-i-cecile So we have a couple of outstanding issues here.

First, there's a system ambiguity which is causing the CI to fail, and which I am unsure how to fix.

Second, there's some unfinished bikeshedding around the naming and role of the TextInput component.

As I see it, we have two choices about how we can structure this:

Choice 1: EditableText is the primary component: it's a widget in it's own right (meaning, there are a bunch of observers associated with it), and the read/write mode is an auxiliary, possibly optional component. If we choose this route, then it makes sense to rename TextInput to something like TextEditable or ReadWriteMode and make it optional.

The biggest problem here is what to do about accessibility: we need to add AccessibilityNode as a required component if we are going to be consistent with the other widgets, and the only two candidates for this are EditableText and TextInput. I don't like the idea of adding a dep from bevy_text to bevy_a11y, since a11y is really about interaction, and is outside of bevy_text's wheelhouse. The only other option is to add the requirement dynamically in the plugin rather than using a Rust annotation / derive macro.

Choice 2: EditableText represents just a buffer, a passive component with no associated event listeners but lots of methods for manipulating text via queued actions; the actual "widget" is TextInput, and it, along with all of its observers, live in bevy_ui_text.

Finally, I want to mention that I need to solve this problem to unblock my work on FeathersNumberInput and the color picker dropdown generally.

I very strongly favor choice 2. I wanted it to change it to this earlier, but it felt irresponsible to demand a big refactor with the 0.19 release coming up.

@viridia

viridia commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Well, the tests are failing, and I'm very confused: the test that is being reported as failing (steam_osk_unidentified_keys_insert_produced_text) doesn't exist, or at least, I can't find it.

@alice-i-cecile

Copy link
Copy Markdown
Member

Freshly added; I just merged in main for you.

@kfc35 kfc35 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transitions between drag states for the number input needs more tweaking.

While testing in feathers gallery:
When I click in to edit a number input value and then click out of it, I expected to be able to scrub the same number input, but instead it seems to insist that I still want to edit it numerically. I would expect that when I click away (lose focus) that I should be able to scrub again. Only after I press enter am I able to pointer scrub again, but the cursor doesn’t make it obvious (it has the typing cursor, not the scrubber one). It only turns back into the scrubber one after I click outside of the number input.

Overall, I think the separation of EditableText and TextInput makes sense to me. I couldn’t find any problems with read-only mode and static. The read-only mode is nice and can come in handy.

I can re-review later if you intend to fix the above problem with number input value, but I also don’t mind approving now and for that to be addressed in a (hopefully quick) follow up

if !input_focus.is_changed() {
return;
}
// React to removal of `TextReadWriteMode`, and drain the whole reader (using `.count()`) so that

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This comment doesn’t make sense to me within the context of this function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed.

@viridia

viridia commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

I think the transitions between drag states for the number input needs more tweaking.

Good catch, that was a real bug; I thought I had caught that case but I missed that one (so...many...observers!). I've pushed a fix.

@kfc35 kfc35 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have a few suggested comments/solutions related to the what the cursor should look like under certain circumstances for the number input. Beyond that, functionally everything seems to work as expected.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I found that after scrubbing a number input once and then hovering back over the number input, the cursor would become the i-beam instead of staying as ColResize because technically it has focus here. I don’t think that makes sense — it should stay as ColResize unless the user has entered edit mode.

If you add a check here to ensure that the EditMode is Editing here, that solves this particular problem.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These are good suggestions - I'll try to work on them today at the club meeting.

Note that I'm not super happy with the ColResize cursor - I would prefer something lighter weight that didn't block the view so much. But unfortunately winit doesn't support the full set of standard CSS cursors: many of the standard winit cursors map to the same icon. Otherwise, I would use the standard EWResize icon, which is a bit smaller.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This specific line looks like a hold-over from the previous logic, I'm going to delete it.

Comment thread crates/bevy_feathers/src/controls/number_input.rs
Comment thread crates/bevy_feathers/src/controls/number_input.rs
@viridia viridia removed the S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged label Jul 25, 2026
@alice-i-cecile alice-i-cecile added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Jul 26, 2026
@DataTriny

Copy link
Copy Markdown
Contributor

FYI there is a big missing piece in terms of accessibility that you may want to consider early on: the content of a text input should not be set as value on the AccessKit node with Role::TextInput. Instead, nodes with Role::TextRun should be added as children of the input node for each span of text. There are a few required properties to set on these nodes: value, bounds, character_lengths, character_positions, character_widths, text_direction, optionally word_starts... Caret position or text selection is then set on the text input node, but anchor and focus refer to text run node IDs.

@viridia

viridia commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

This was a challenging merge (text input + new theming system).

@viridia

viridia commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

FYI there is a big missing piece in terms of accessibility that you may want to consider early on: the content of a text input should not be set as value on the AccessKit node with Role::TextInput. Instead, nodes with Role::TextRun should be added as children of the input node for each span of text. There are a few required properties to set on these nodes: value, bounds, character_lengths, character_positions, character_widths, text_direction, optionally word_starts... Caret position or text selection is then set on the text input node, but anchor and focus refer to text run node IDs.

What you are describing seems like the kind of thing that would be appropriate for a rich text editor, not a simple string editor like this.

@DataTriny

Copy link
Copy Markdown
Contributor

What you are describing seems like the kind of thing that would be appropriate for a rich text editor, not a simple string editor like this.

All platform accessibility APIs allow assistive technologies to navigate inside text content using some form of range API. If Bevy's text inputs don't expose their content as text runs, they will appear as read-only and without caret to assistive technologies.

@viridia

viridia commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

All platform accessibility APIs allow assistive technologies to navigate inside text content using some form of range API. If Bevy's text inputs don't expose their content as text runs, they will appear as read-only and without caret to assistive technologies.

That seems like it would be difficult to implement, as we have no concept of a "text run" in this context. While bevy does have text runs for static text blocks, for text input it's just a plain string - things like rich text or syntax highlighting have been declared out of scope, and would likely be supported by third-party crates, not the core engine.

I'm curious as to what the recommendation would be for something like a username or password login field?

@DataTriny

Copy link
Copy Markdown
Contributor

While its API is not ideal, parley has an AccessKit integration gated behind the "accesskit" Cargo feature which can easily support small text inputs. I'm guessing that the biggest open question would be how to pick node IDs for the text runs.

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 M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Contentious There are nontrivial implications that should be thought through

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.