Simple single line text field
Motivation
Currently, we don't have any widget that allows editing text or component values. So I decided to create a simple version of such a field.
Proposal
I created two components
LineTextField
- Single-line text editing
- Cursor positioning and text selection
- Clipboard operations (copy, cut, paste, select all | Ctrl-C,X,V,A)
- Support for text longer than field width
- Customizable allowed characters
NumericField<T>
- Supports multiple numeric types (u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64)
- Text-based input for precise value entry
- Mouse drag functionality for quick value adjustments
- Configurable min/max value constraints
- Customizable drag step (drag step can be less then 1 val per logical pixel)
And three crates
bevy_focus
This crate contains input focus managment
bevy_text_field
This crate contains text field logic. Builded on top of bevy_focus.
bevy_numeric_field
This crate builded on top of bevy_text_field and contains logic for NumericField<T>
Usage
// LineTextField
commands.spawn((
NodeBundle { /* ... */ },
LineTextField::new("Initial text"),
)).observ(|trigger: Trigger<TextChanged>| {
let text = trigger.event().0; //Do whatever with text
});
// Update text
let mut text_field = LineTextField::default();
text_field.set_text("bla bla bla");
// NumericField
commands.spawn((
NodeBundle { /* ... */ },
NumericField::<f32>::new(0.0),
)).observe(|trigger: Trigger<NewValue<f32>>| {
let new_value = trigger.event().0;
// Handle value change
});
// Update number
// We must use trigger because its contains complex logic for updating
commands.trigger_targets(SetValue(0.0), translation_x);
Testing
I created three examples for manual testing:
- single_line_text_field.rs - contains three size different single line text fields
- many_fields - contains all supported numeric fields
- transform.rs - contains mvp for changing transform with new numeric fields
- NumericField uses LineTextField in this work
The following features are not planned for this PR
- Multi-line editing
- Precise cursor positioning by clicking on the text field with the mouse
Followed work
- Move to i-cant-belive-this-is-not-bsn crate
- Move to Required components
- Create new SerializedField component, which allow to use LineTextField for any strut that support serialize/deserialize
- Create design for split huge LineTextField to nice sub components, which will allow to huge customization of this text field
- Unhardcode key bindings for copy/paste/cut
- Change cursor from byte position into char number position for more safety for non-english text
- Found a way to avoid invisible text nodes and text splitting
Rather than have specific types for int and float, I would prefer a way to have a validator.
I had at one point started to build a text input field for quill and bevy_reactor, but this got put on hold due to the cosmic-text rework. However, my plan was to architect this just like form fields are done on the web: when the user types a key, the input field emits an "on_change" event, which bubbles up the hierarchy. The listener can inspect the text and decide if it's valid, if not it takes steps such as canceling the edit or showing an error message or whatever.
This lets you do things like have validation rules for email addresses, phone numbers, passwords, and so on.
As far as dragging goes, I would rather that be something that the third-party widget library adds on top of the basic text widget, by installing the appropriate observers. I don't think it should be built-in.
In the web world, there are many, many different widget toolkits competing against each other, and I think this is healthy. I want to see different third-party widget libraries in the Bevy ecosystem, and I think that these libraries need to have the freedom to customize their interaction behavior. Bevy should handle the gnarly stuff like calculating selection rectangles for BiDi text and handling non-English input methods, and provide a platform for experimentation into different kinds of interaction and presentation.
Yes, the approach with validators is indeed a good one. I'm actually planning to experiment with it, limiting myself to three options: text without validation, text with float, and text with int. I'm considering using two traits: SerializableValue and DraggableValue, within which I'll separate the logic for int and float on top of the text field.
However, I'm not quite clear on the second part of the comment. I'm curious about how the multitude of existing UI libraries relates to the editor prototype. The goals of this repository state that we're not using third-party UIs, only bevy_ui. My intention is to create a widget for the editor prototype that could be used right now, rather than waiting for Bevy to improve its UI capabilities. (In any case, there will be several iterations of improvements for both text fields and reactivity in general, but I would like to avoid overengineering the solution before we have even completed the first step of the editor prototype.)
I'm assuming that the editable text field is going to be used for something besides editors.
Lots of games need text input:
- name your character
- name this saved game
- type your username and password to log in to the network.
A simple single-line text editing widget would be very useful for all of these. But it should be flexible enough to work with games that have their own distinct visual style.
Alternatively, instead of providing a text-edit widget, we could provide an API (exposing the cosmic-edit methods) to let people build their own edit widgets.
It's true that a text field builder would be very useful. But such a constructor should be in bevy_ui crate. This is outside the scope of the current repository
Yep, I'm fine with a very simple design to start.
one other thing i think would be really useful to add is ctrl + a for full text select
@rewin123 I've finally had a chance to take a look at this.
Things like:
- Focusing on single text input.
- Special-cased support for numeric field entry.
- Clipboard support!
- Usage of observers as the core interaction paradigm :)
There's two blocking organizational concerns:
- I don't think that the composition of crates / abstractions is quite right. I'd like a text input primitive, which gets wrapped into widgets that define interactive fields. I think that the right split is
bevy_text_editing+bevy_field_formsor something. - Clipboard functionality needs to be its own crate: this is something we'll want to build on cohesively, rather than something coupled to text input widgets.
Non-blocking notes:
- I'm not thrilled with having another focus management abstraction when we're planning to build something upstream for 0.16. This is fine for now, but be prepared to swap off it.
- Long-term I'd really like to use the cosmic-edit functionality for text input in Bevy.
@alice-i-cecile Thank you for the review! I will make the necessary adjustments based on the feedback received, including the more generalized text editing functionality, which will be moved to a separate crate called bevy_text_editing. Regarding the non-blocking notes:
-
I wasn't aware that such a focus system was planned for implementation. If it's already available for use, I would be delighted to incorporate it!
-
During the implementation process, I couldn't find much information about text editing capabilities in cosmic crate. I only came across some IME mode functionality, the purpose of which isn't entirely clear to me. (Wikipedia suggested that it's only Windows-specific window interaction.) If there's a link to clear information about existing text editing primitives within the cosmic crate, I would be more than happy to refactor my home-made editor to use something more functional.
See https://github.com/bevyengine/bevy/pull/15611 :) I'm hoping to merge that PR and form a working group for it in 0.16, and then swap this repo to the updated main and co-develop it.
On the topic of cosmic-edit, see https://github.com/pop-os/cosmic-edit :) There's a WIP integration over at https://crates.io/crates/bevy_cosmic_edit that we can learn from.
The keyboard focus PR #15611 is currently on hold, and won't be merged until after the 0.15 release. There are several follow-up PRs planned, which include integrating with bevy_a11y and adding a tab-navigation plugin system. (Because consoles also use focus navigation, controlled by the gamepad, we want to support different navigation strategies).
@alice-i-cecile I want to talk about strategy here. My understanding is that the current strategy for the editor is to plough full speed ahead with a minimum of blockers, even if that results in potential duplication and rework later - because of growing pressure to ship something, even if it's not perfect. If that's the case, then this PR should probably go ahead without blocking on the focus system - with the understanding that it will probably be completely re-written later.
To be clear: we have the option to check in an "editor specific" text input field now, and later replace it with a general "editor and games" text input field later. I don't think there's any chance of implementing the latter quickly - not because it's particularly difficult, but because of blockers and dependencies.
There are a number of "things that I would have done different" about this PR: As mentioned, I would leverage parts of cosmic-edit. At the very least, I would use the methods on cosmic Buffer, and CursorMotion to handle calculations of cursor movement. Whether we would use the full Editor functionality is to be determined.
Also, as mentioned in the lorem-ipsum discussion on Discord, writing a multi-line editor isn't much more difficult than writing a single-line editor, assuming you already have code to display word-wrapped text (which we do). Cosmic's cursor motion should be capable of handling the up and down arrow key movement for example.
A general-purpose text input for games needs some additional features that are not needed for the editor, mainly it needs full customization of colors and fonts. So the text, background, cursor, and selection highlight all need to be configurable.
I'm also a proponent of the "controlled" vs. "uncontrolled" design pattern for widgets. This is an idea from React, "controlled" widgets are ones that don't update their state directly. So for example, a "controlled" text input reacts to text changes by emitting a "TextChange" event, which bubbles up the hierarchy. The event contains a copy of the proposed new state of the widget. The receiver can examine the proposed change and decide whether or not to apply the state to the widget. If it chooses to ignore the event, then the key press does nothing. Otherwise, the receiver can inform the widget (via method call on the component) to update to the new state.
"Uncontrolled" widgets, on the other hand, update their state automatically, and inform their listeners after the fact. Uncontrolled widgets have slightly snappier performance (which shouldn't be an issue here). Controlled widgets are more versatile:
- Controlled widgets permit a single source of truth when you have a text input field that is also updated programmatically, such as a hex color input field that also changes when you drag the color sliders.
- Special purpose input fields such as integers, floats, email addresses, phone numbers, hex colors and so on are trivial to write as a layer on top of a controlled widget - the receiver can examine the proposed change, and if there's a non-numeric digit or a character out of place it can veto the change.
React allows both controlled and uncontrolled text input widgets. Many developers, myself included, prefer controlled widgets and use them pretty much universally.
Finally, I would like to see a text input that supports BiDi. In BiDi editing, it's possible to have text that reverses direction in mid-string, which can result in a non-contiguous selection - this means that the selection highlight may consist of multiple rectangles (something that already can happen with multi-line editing). Supporting BiDi properly is a hard problem, but I'm assuming that cosmic-edit would handle all the complexities for us. (If it doesn't, then we have a problem).
@alice-i-cecile I want to talk about strategy here. My understanding is that the current strategy for the editor is to plough full speed ahead with a minimum of blockers, even if that results in potential duplication and rework later - because of growing pressure to ship something, even if it's not perfect. If that's the case, then this PR should probably go ahead without blocking on the focus system - with the understanding that it will probably be completely re-written later.
Yep, that's my broad strategy here. I'm actually less motivated by an urge to ship something, than a desire to have something people can touch so we can start actually iterating on the hardest problems: editor-game interop, extensions, undo and so on. Without a toy version of an editor in place, there hasn't been appetite to tackle this in the past.
Like you said, I think that this is approximately good enough for those goals for now :) I do think that "build good widgets" is a valuable secondary goal too though, so refining it further in follow-up is wise.
Finally, I would like to see a text input that supports BiDi. In BiDi editing, it's possible to have text that reverses direction in mid-string, which can result in a non-contiguous selection - this means that the selection highlight may consist of multiple rectangles (something that already can happen with multi-line editing). Supporting BiDi properly is a hard problem, but I'm assuming that cosmic-edit would handle all the complexities for us. (If it doesn't, then we have a problem).
@viridia Do I understand correctly that you are proposing to make an upstream of the bevy_cosmic_edit library in this repo?
I have not looked at bevy_cosmic_edit much, although I understand that it is a bridge between Bevy and cosmic-edit. I don't think it makes sense to try and upstream it directly - bevy_cosmic_edit has existed since June of 2023, long before Bevy switched over to using cosmic-text for its text rendering duties, so I suspect that many parts of it are now obsolete.
I think what Alice is suggesting is that bevy_cosmic_edit might be a good source of ideas and lessons learned.
Since Bevy now has a dependency on cosmic-text anyway, I think it makes a lot of sense to re-use parts of cosmic that relate to editing. After all, the entire goal of cosmic-text is to build a world-class editor in Rust, and this includes solving all the subtle details involved in implementing advanced editing features. At the same time, Bevy is all about keeping dependencies lightweight and minimal, so exactly how much of cosmic-edit we want to use is still to be determined. For example, we're already using the cosmic text Buffer type (renamed CosmicBuffer in Bevy), and that type has a number of editing methods that we should probably take advantage of. In particular, we should rely on cosmic's CursorMotion type for calculating the effects of Home / End / PageUp / PageDown keys and so on.
@alice-i-cecile @viridia
Unfortunately, as of the latest commit in Bevy, all external access to CosmicBuffer has been cut off, so I can't use any of the Cosmic functionality without modifying Bevy itself. Upstreaming bevy_cosmic_edit directly seems like a bad idea, as it's completely custom and doesn't use the Text struct in any way and would require simply removing it when better text code will be ready, and there's still the question of maintaining its large codebase to latest bevy. If you don't mind, I would like to postpone work on BiDi text and multiline text. Or I'm waiting for your any another suggestions.
Code from bevy
/// Computed information for a text block.
///
/// See [`TextLayout`].
///
/// Automatically updated by 2d and UI text systems.
#[derive(Component, Debug, Clone)]
pub struct ComputedTextBlock {
/// Buffer for managing text layout and creating [`TextLayoutInfo`].
///
/// This is private because buffer contents are always refreshed from ECS state when writing glyphs to
/// `TextLayoutInfo`. If you want to control the buffer contents manually or use the `cosmic-text`
/// editor, then you need to not use `TextLayout` and instead manually implement the conversion to
/// `TextLayoutInfo`.
pub(crate) buffer: CosmicBuffer,
/// Entities for all text spans in the block, including the root-level text.
///
/// The [`TextEntity::depth`] field can be used to reconstruct the hierarchy.
pub(crate) entities: SmallVec<[TextEntity; 1]>,
/// Flag set when any change has been made to this block that should cause it to be rerendered.
///
/// Includes:
/// - [`TextLayout`] changes.
/// - [`TextFont`] or `Text2d`/`Text`/`TextSpan` changes anywhere in the block's entity hierarchy.
// TODO: This encompasses both structural changes like font size or justification and non-structural
// changes like text color and font smoothing. This field currently causes UI to 'remeasure' text, even if
// the actual changes are non-structural and can be handled by only rerendering and not remeasuring. A full
// solution would probably require splitting TextLayout and TextFont into structural/non-structural
// components for more granular change detection. A cost/benefit analysis is needed.
pub(crate) needs_rerender: bool,
}
Sounds good :) Totally fine to punt on these harder questions for now. If you find that access to more internals in Bevy would be helpful in the future, please feel free to open PRs there and we can get them merged and then update the Bevy version we're using here.
I see this as having multiple phases:
Phase 1: Implement a single-line text input field with minimal features that is part of the editor, and only used by the editor, based on the current PR. Phase 2: Change Bevy to expose the editing capabilities of the text component and cosmic buffer (or define a new text component specifically for editing). Phase 3: Write a general-purpose text input widget, with the capabilities that I have outlined here and in the discord discussion, which is useful for both editors and games. If needed, I can come up with a detailed specification (I've written text input widgets for several game frameworks previously, so I understand the issues involved). Phase 4: Revise or replace the simple editor from phase 1 to use the new editing capabilities defined in phase 3.
edit: Sorry, GitHub had pushed many recent comments into "see 53 more lines", which, I assumed, would only be commits, but it also hides relevant discussions. Please ignore my comment if this has been discussed earlier.
I love the work being done here.
I do want to point out that a +4300 lines PR for "simple single line text field" seems excessive. Looking at the code, I see (f.e.) bevy_incomplete_bsn as a new crate.
Would it make more sense to split those up into separate PRs, to unblock less controversial (or less complex) code that fits well as a single (utility) crate?
edit: Sorry, GitHub had pushed many recent comments into "see 53 more lines", which, I assumed, would only be commits, but it also hides relevant discussions. Please ignore my comment if this has been discussed earlier.
I love the work being done here.
I do want to point out that a +4300 lines PR for "simple single line text field" seems excessive. Looking at the code, I see (f.e.)
bevy_incomplete_bsnas a new crate.Would it make more sense to split those up into separate PRs, to unblock less controversial (or less complex) code that fits well as a single (utility) crate?
The bevy_incomplete_bsn will be cut from this PR when it is ready. In this case, I'm using it to stress test a text field. Unfortunately, the stresstesting found two bugs that need to be fixed. Unfortunately in 1.5 weeks I never got to sit down to fix them due to increased work load. I hope to be able to do it next week and prepare two PRs - this one and the “component inspector” pane.
I deeply apologize for disappearing. Preparation for my PhD defense consumed all my free time last month. On the other hand, I successfully defended my PhD last week, so starting today I'm returning to finish this PR. (If it's still relevant).
Congrats, and about the project - it is relevant, I really like at the point it is rn, and I'm sure it will be better. You will have some work to do though, theres been a lot of changes with the editor
Finally found a bug and everything works almost as intended. It remains only to understand why sometimes at startup the text field is not filled with the value of the component and I will prepare a PR for review
FYI, we've merged InputFocus upstream. Could you use that here to avoid duplication and help us find problems with it?
FYI, we've merged
InputFocusupstream. Could you use that here to avoid duplication and help us find problems with it?
Awesome. I will try to use it
@alice-i-cecile I tried to switch to the commit with the last state of InputFocus crate (bevy commit with last InputFocus state), but got a lot of errors in other crates. Would it be reasonable to merge this PR with the current version of bevy, and then fix errors and implement InputFocus in a separate PR? Overall, I've fixed everything I wanted and could.
Sounds good; let's merge this and update Bevy in a separate PR.