# Objective
Add a way to change the justification for the text input in the
multiline_text_input example.
## Solution
Add a pop up menu that lets you set justify.
# Objective
Make the `multiple_text_inputs` example a bit more useful by giving each
text input a different justification, which should help with debugging
cursor behaviour.
## Solution
Add an input for each `Justify` variant along with a column on the left
labelling the justification it is using.
## Showcase
<img width="1798" height="750" alt="inputs-justified"
src="https://github.com/user-attachments/assets/324c1cd4-b6a1-4c0f-ad1a-38cd28867704"
/>
The IME support example configures its editable text field as a
single visible input, but the text layout still used default soft
wrapping. When the cursor moved through long text, wrapped visual lines
could appear as disconnected text segments.
Set the example input to use `TextLayout::no_wrap()`, matching the
other single-line text input examples while still allowing explicit
newlines.
Fixes https://github.com/bevyengine/bevy/issues/23932.
```sh
cargo check --example ime_support --features system_font_discovery
```
# Objective
- When `FontSource` resolution fails because an asset isn't yet loaded,
it doesn't attempt to reresolve the `FontSource` again the next frame.
- When a `Font` asset is removed, its font data is not unloaded from
Parley's font database
- The results from font lookups can be inconsistant when using `Handle`s
to identify a font.
Fixes#24356
## Solution
* In `load_font_assets_into_font_collection`, register each font twice,
once with an internal asset-specific alias for handle lookups and once
using its embedded family name.
* Use `set_changed` on `TextFont` to trigger chain detection, instead of
the `needs_rerender` flag on `TextBlock`. Schedule
`load_font_assets_into_collection` to run before
`detect_text_needs_rerender`, otherwise this would delay updates for a
frame.
* Store the changed family ids and asset paths, and set any `TextFont`s
that refer to them as changed.
* Don't update the measure funcs in `update_editable_text_content_size`
on font asset changes. Instead rely on the narrower `TextFont` change
detection, which is sufficient now because
`load_font_assets_into_font_collection` marks affected `TextFont`
components as changed when newly loaded font assets can affect font
resolution.
* Removed the mutable deref of `EditableText` at the start of
`update_editable_text_styles`. The editor is only mutable accessed if
updates to the styles need to be made.
* On unloading a font rebuild the whole font database, minus the
unloaded fonts, remap any generic families and relayout all text.
* Keep a copy of the registered generic families in `FontCx`, so they
can be remapped after unloading a font.
## Testing
```rust
use bevy::{
feathers::{controls::FeathersNumberInput, FeathersPlugins},
prelude::*,
};
fn main() {
App::new()
.add_plugins((DefaultPlugins, FeathersPlugins))
.add_systems(Startup, setup)
.add_systems(Update, spawn_number_input)
.run();
}
fn setup(mut commands: Commands) {
// ui camera
commands.spawn(Camera2d);
}
fn spawn_number_input(mut commands: Commands, mut is_spawned: Local<bool>) {
if *is_spawned {
return;
}
*is_spawned = true;
commands.spawn_scene(bsn! {
Node {
margin: UiRect::all(auto())
}
Outline
Children [
:FeathersNumberInput Node { width: px(200) }
]
});
}
```
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
Only select all on focus with `SelectAllOnFocus`.
It should be possible to have single line inputs without automatic
select all on focus.
## Solution
Remove the automatic select all on focus for single line inputs from
`on_focus_select_all`.
# Objective
Clean up the `text_input` example a bit.
## Solution
- Removed some unneeded style constraints.
- Disable newlines, disable wrapping, and set a responsive width for the
inputs.
- Submit on Enter, not Ctrl + Enter.
- Center the example using an auto margin.
---------
Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
# Objective
Little bit of clean up for the `ime_support` example.
## Solution
* Use `row_gap` on the parent for spacing instead of setting individual
margins on the children.
* Give the text input node a fixed height and allow newlines.
## Testing
```
cargo run --example ime_support --features="system_font_discovery"
```
# Objective
In the `multiple_text_inputs` example, the input focus should move to
the next text input after the user submits the input's contents.
## Solution
- In `submit_text`, after submission, use the `TabNavigation` system
param to find the next input, and set it as the `InputFocus`.
- Submit on just `Enter`. The `Ctrl` + `Enter` chord should be reserved
for inputs that allow new lines.
# Objective
Bevy currently supports OpenType `FontFeatures`, but it doesn't support
`FontVariations`, although `parley` has support for them.
Font features are mainly on/off values to vary the font (`u32`), while
font variations are a continuous range of values that can be set to vary
the font (`f32`).
I personally need it for setting `FILL` on the material design icon
font.
## Solution
I implemented `FontVariations` as a separate struct from `FontFeatures`
for now and copied it's api.
However, since both are so similar it's worth considering to merge them
into something like a `FontVariables` with a builder that has
`set_feature` and `set_variation` methods.
## Testing
Added a new example demonstrating how to set the font weight using
`FontVariations` instead of `FontWeight`.
(Setting font weight using `FontFeatures` does not work, although the
documentation suggests it should. I'm not sure if this depends on the
variable font used or whether this is an error in the documentation.)
## Showcase
<img width="946" height="541" alt="Screenshot 2026-05-02 225528"
src="https://github.com/user-attachments/assets/9067191b-8517-4fec-9283-45e57180658a"
/>
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
# Objective
Fix some scrolling jitters and bugs.
* The cursor can be scrolled out of view.
* Horizontal scrolling is jittery with a cursor taller then the line
height.
* Vertical scrolling jitters.
* Scrolling and cursor state should only update on changes to the editor
state and input focus (this is needed so scroll bars will work with a
text input).
Fixes#23931
## Solution
* Use the full cursor width when calculating the horizontal scroll
offset.
* Base vertical offsets on the bounds of the current visual line, not
the cursor.
* Update scrolling on `InputFocus` changes.
* `TextLayoutInfo` always holds the cursor geometry (if there is a
cursor), made the field a `(bool, Rect)` tuple, the bool is true if the
cursor should be rendered.
* Lots of clamping, logic behind it mostly trial and error tbh.
This should fix the major scrolling bugs, scrolling should feel
consistant and intuitive now.
Left look-ahead scrolling
https://github.com/bevyengine/bevy/issues/23933 left for a follow up,
after this is merged.
## Testing
```
cargo run --example multiple_text_inputs
cargo run --example multiline_text_input
cargo run --example ime_support --features="system_font_discovery"
```
With `ime_support` and `multiline_text_input`, you probably want to set
`TextLayout::new_with_no_wrap()` on the inputs to fully appreciate the
changes.
# Objective
Remove the `new_with_` prefixes from the `TextLayout` constuctor
functions. Generally, the "new" part is redundant and "with" is used by
fluent APIs.
## Solution
Just delete the prefixes, shorten the names (all on `TextLayout`).
* `new_with_justify` -> `justify`
* `new_with_linebreak` -> `linebreak`
* `new_with_no_wrap` -> `no_wrap`
# Objective
Implement minimal multi-click support.
Add double click to select word and triple click to select all support
to text input widgets.
Fixes#23874
## Solution
New `MULTI_CLICK_DURATION` constant that sets the max time between
clicks for them to count as consecutive.
Added a field `count` to `Click`. 1 is a single click, 2 is a double
click, 3 is a triple click and so on. It's a `u8` and saturates at 255
if you click that many times.
Current multi-click state is stored in a field `clicking` on
`PointerButtonState`.
The `clicking` state is cleared after `MULTI_CLICK_DURATION` without
another click.
New `SelectWordAtPoint` `TextEdit`.
New observer system `on_pointer_click` in `bevy_ui_widgets::text_input`.
This queues `SelectWordAtPoint` for 2 clicks and `SelectAll` for 3 or
more. `Click` events are dispatched on release, so they happen after
`Press`. The `Press` for a double click might move the cursor but that
doesn't affect the `SelectWordAtPoint` edit that is dispatched.
#
The implementation is deliberately minimal, I just added here what was
needed for the text input. Left for followups:
- Tracking the initial click position and returning the displacement
with `Click`.
- Spatial tolerance (clicks are only multi-clicks if the pointer hasn't
moved too far).
- Configurable multi-click duration.
We could add a separate `MultiClick` event instead. It seems to me
things would be more complicated with both `Click` and `MultiClick`
observer systems though.
## Testing
Text inputs now support double click to select a word and triple click
to select all:
```
cargo run --example multiline_text_input
```
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Talin <viridia@gmail.com>
# Objective
- Text input is one of the headline feature in Bevy 0.19.
- Our defaults are currently very ugly, and shown across our user-facing
text examples.
- Good default matter for user perception of quality, and to make quick
prototyping easier.
- Additionally, the layout in the text_input example is erratic in a way
that looks poorly made.
Fixes#23955.
## Solution
1. Clean up the default colors for text input by picking boring,
uncontroversial tailwind colors.
2. Change the css::YELLOW borders in our text input examples to
something neutral that looks nice with our background.
3. Fix up the layout for the `text_input` example so it's both simpler
and looks much better.
## Testing
`cargo run --example text_input`
## Showcase
Before:
<img width="873" height="773" alt="image"
src="https://github.com/user-attachments/assets/bcc70bf6-e6b1-4bf4-b671-7924ac24e984"
/>
After:
<img width="757" height="274" alt="image"
src="https://github.com/user-attachments/assets/e1a3e325-b3a7-4a9b-831e-6886e6a818fc"
/>
# Objective
Add a platform-agnostic interface for interacting with the clipboard.
## Solution
New crate `bevy_clipboard` with a `ClipboardPlugin` that adds a
`Clipboard` resource. The clipboard is accessed using the methods
`fetch_text`, `fetch_image`, `set_text` and `set_image` on the
`Clipboard` resource. `fetch_text` returns a `ClipboardRead` with a
`poll_result` method that's used to get the actual value once it's
ready.
The `windows` and `unix` implementations both use the `arboard` crate.
On windows the `Clipboard` resource is a unit struct and a new arboard
clipboard instance is created and dropped for each clipboard access. On
unix targets the `Clipboard` resource holds a clipboard instance it
reuses each time. On both targets the `fetch_*` and `set_*` methods work
instantly.
On `wasm32` `Clipboard` is a unit struct. The `fetch_text` and
`set_text` functions spawn async tasks. The task spawned by `fetch_text`
updates the shared arc mutex option once the future is evaluated to get
the clipboard text. There is no image support on `wasm32`.
Everything seems to work but it feels like the design is a bit clumsy
and not very idiomatic. I don't tend to do much asynchronous
programming, maybe a reviewer can suggest an improved construction.
I also added an alternative `fetch_text_async` function for async access
that returns a `Result<String, ClipboardError>` future.
### Notes
* Doesn't support android targets yet.
* The wasm32 implementation doesn't support images. It's much more
complicated and probably best to left to a follow up.
## Testing
The PR includes a basic example `clipboard` that can be used for
testing.
The image display will only work if the image is already in the
clipboard before the example starts.
---------
Co-authored-by: Gilles Henaux <ghx_github_priv@fastmail.com>
Co-authored-by: Andrew Zhurov <zhurov.andrew@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
IME support during text input is extremely important for non-Latin
languages: notably the CJK family (Chinese, Japanese, Korean). It would
be very nice to support it in our initial release!
Fixes#23795.
## Solution
1. Peek at the `input/text_input` example that @mockersf made and I
forgot about.
2. Steal its strategy and wire up the IME events to Bevy's proper text
input widgets.
3. Delete the now-obsolete example.
4. Add underlines to tentative characters (exposed by parley :D) so then
users can distinguish what they're typing from what they have typed.
5. Rename `editable_text` to the nicer `text_input`.
6. Create a dedicated example for `ime_support`, which uses system
fonts.
7. Add an `error_once!` to fix a footgun I tripped on when using system
fonts...
Per @mockersf's complaints, I've opted not to ship any new fonts in this
PR: the system fonts actually worked great!
## Testing
I've setup IME support on Windows, and added a small font with Japanese
support to the `editable_text` example.
It works quite well! We can even submit the values!
Known issues:
- when entering Japanese characters, the console is spammed with "ICU4X
data error: No segmentation model found for language: ja". I have no
idea where this is coming from: I think a system dependency is causing
this, and it's bubbled up by `parley`? IDK. Doesn't happen with Latin
characters, and it renders fine so...
-~~tab selecting the IME suggestion causes the selected text entry box
to change. I wasn't sure how we want to fix that, so Ieft it for now.~~
EDIT: fixed!
## Showcase
<img width="470" height="233" alt="image"
src="https://github.com/user-attachments/assets/8f8721b3-6746-46d5-8a20-40de634f52e5"
/>
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
# Objective
Improved change detection for `EditableText`.
Fixes#23793
## Solution
* Remove the `text_edited` field from `EditableText` that was used for
manual change detection.
* Use the `PlainEditor`'s `Generation` to track changes.
* New component `EditableTextGeneration`. Newtypes `parley::Generation`.
Stores the generation from the last `TextLayoutInfo` update.
* If `EditableText::editor`'s and `EditableTextGeneration`'s generation
values aren't equal, reupdate `TextLayoutInfo`.
* Added support for `TextLayout::justify`.
* Split up `editable_text_system` into two systems
`update_editable_text_styles` and `update_editable_text_layout`. The
text input's style values need to be updated before layout, so the
measure func returns the correct size for the text layout.
* New `EditableText` `testbed_ui` scene.
* Added two numeric inputs to `multiline_text_input` that allow you to
set the height of the multiline input and its fontsize.
* Update selection rects on all changes, not just when a text input is
focused.
## Testing
```
cargo run --example multiline_text_input
```
The cursor appears to be missing for the numeric inputs in the example
but it isn't. The cursor gets clipped because it's at the end of the
right aligned input value text. If you press left it comes into view.
Cursor and scrolling behaviour needs some adjustments but that's out of
the scope of this PR.
# Objective
Add an option to reject `TextEdit`s based on a per character filter.
## Solution
* New component: `EditableTextFilter`. Can be used to set a per
character filter for an `EditableText` entity.
* `TextEdit::insert` and `TextEdit::Paste` edits are ignored unless all
their characters pass the filter.
The filter does not apply to characters already within the
`EditableText`'s text buffer
Initially I thought it would be better to implement the filter at the
widget level, then a key press that results in a rejected edit could be
propagated, but the clipboard isn't available to the keyboard observer
function so paste edits would still need to be filtered when the input
buffer is applied.
I made a branch adding a `TextEditRejected` entity event as an
alternative (that would also notify when an edit failed due to the
`max_characters` limit being exceeded), but left it out of this PR to
keep this one focused on just the filtering.
## Testing
New example:
```
cargo run --example editable_text_filter
```
# Objective
It should be simple to set an initial value for `EditableText`'s text
buffer.
Otherwise I can see users struggling with this and using some ghastly
construction like a run once system in `Update` to set initial text.
## Solution
New `EditableText::new` method. The value of the text buffer is set
immediately and a `TextEdit::TextEnd(false)` edit is queued to move the
cursor to the end of the text.
## Testing
Added some initial text to the multiple text example:
```
cargo run --example multiple_text_inputs
```
# Objective
`EditableText`'s selection rects are currently drawn on top of the text.
Instead they should be drawn below with an option to draw the selected
text in an alternative color.
## Solution
* New field on `selected_text_color: Option<Color>` on
`TextCursorStyle`.
* In `extract_text_sections`, if `selected_text_color` is `Some` and the
glyph is inside a selection rect, use `selected_text_color` to draw the
glyph.
* The z offset for text selection rects is changed so the selection
rects are drawn behind the glyphs.
## Testing
```
cargo run --example multiline_text_input
```
---------
Co-authored-by: Daniel Skates <zeophlite@gmail.com>
# Objective
Add basic support for scrolling text horizontally and vertically to
EditableText.
## Solution
* New component `TextScroll`. Wraps a Vec2 offset that can be used to
set the scroll position.
* Clip overflowing editable text content automatically.
* New system `scroll_editable_text` sets `TextScroll` to keep the cursor
within view inside the `EditableText`'s content box.
* New `allow_newline` flag on `EditableText`. Allows insertion of
newlines with the enter key.
* `editable_text_system` previously ignored `TextLayout::linebreak`. Now
it's possible to change the line break settings.
* Scrolling only happens after a `TextEdit` is applied.
The implementation here is extremely simple but it has some flaws:
* Depending on the width of the node sometimes the cursor can get
clipped.
* `allow_newlines: false` doesn't block pasting in a section of text
with a newline
* No support for scroll margins yet.
* Drag-to-scroll seems to work, but I'm not super confident in it and it
may need some changes.
Went back and forth over whether `ScrollPosition` should be used instead
of a dedicated component, I think it's probably simpler with
`TextScroll` but not certain. Clipping doesn't use `Node`'s overflow API
and is handled automatically during extraction, always targeting the
content box.
## Testing
```
cargo run --example multiline_text_input
```
# Objective
- Example `editable_text` crashes because of duplicated plugins
## Solution
- Remove the duplicated plugins
## Testing
- `cargo run -example editable_text`
# Objective
A few minor changes for the `editable_text` example.
## Solution
* Use `px` and `Val2::px` helpers.
* No need to clone the `SplitString` from `EditableText::value`.
# Objective
Add a max characters limit to `EditableText`
## Solution
- New field `max_characters: Option<usize>` on `EditableText`.
- Queued `TextEdit`s that would cause the text to exceed
`max_characters` (if some) are ignored.
## Testing
```
cargo run --example editable_text
```
The right input's max character limit is set to 7.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
Add support to set a text input’s height based on a given number of
visible lines.
## Solution
- Added a `visible_lines: Option<f32>` field to `EditableText`.
- New system `update_editable_text_content_size` that runs in
`UiSystems::Content.
If `visible_lines` is `Some`, `UiSystems::Content` sets a `ContentSize`
that determines the node's height as `line_height * visible_lines`,
using the resolved font line height.
## Testing
Updated the example:
```
cargo run --example editable_text
```
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
- Implements the `LetterSpacing` feature from issue #8781, the
`LineHeight` is already implemented.
## Solution
Connects Bevy's text pipeline to `parley`'s built-in letter spacing
support,
mirroring the approach used for `LineHeight`.
`Rem` and percentage-based sizing are not yet implemented.
## Testing
- Added a `letter_spacing` example that visually validates the feature.
- Fixed AABB-related tests for `text2d`.
- Reviewers can run: `cargo run --example letter_spacing`
- Works like `LineHeight`, but currently only supports `Px()`.
- Note: letter spacing is currently a required field —
open to making it optional if that's preferred.
## Showcase
`cargo run --example letter_spacing`
Or here is a video about it:
[Kooha-2026-03-16-10-14-23.webm](https://github.com/user-attachments/assets/3d778bd0-c8bc-43d3-b61b-14ae23c3b906)
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
# Objective
- `glam`, `hexasphere` & `rand` have released their latest versions,
update Bevy to support them.
## Solution
- The above have been updated to their compatible versions. `rand_distr`
updated as well to match `rand` v0.10 support.
- `rand_chacha` is soft deprecated and no longer used by `rand`, so its
usage has been changed to `chacha20` to match `rand` dep tree.
- `uuid` is in the process of updating to `getrandom` v0.4, which `rand`
v0.10 supports. This PR remains in draft until a new `uuid` release hits
crates.io.
- `RngCore` is now `Rng`, and `Rng` is now `RngExt`, so this required
updating across many files.
- `choose_multiple` method is deprecated, changed to `sample`.
## Testing
- Chase all compiler errors, since this should not regress any already
existing behaviour.
- This must pass CI without regressions.
## Additional Notes
`getrandom` v0.4 doesn't add anything new for Web WASM support, so the
same `wasm_js` feature is used.
# Objective
Add responsive font sizes supporting rem and viewport units to
`bevy_text` with minimal changes to the APIs and systems.
## Solution
Introduce a new `FontSize` enum:
```rust
pub enum FontSize {
/// Font Size in logical pixels.
Px(f32),
/// Font size as a percentage of the viewport width.
Vw(f32),
/// Font size as a percentage of the viewport height.
Vh(f32),
/// Font size as a percentage of the smaller of the viewport width and height.
VMin(f32),
/// Font size as a percentage of the larger of the viewport width and height.
VMax(f32),
/// Font Size relative to the value of the `RemSize` resource.
Rem(f32),
}
```
This replaces the `f32` value of `TextFont`'s `font_size` field.
The viewport variants work the same way as their respective `Val`
counterparts.
`Rem` values are multiplied by the value of the `RemSize` resource
(which newtypes an `f32`).
`FontSize` provides an `eval` method that takes a logical viewport size
and rem base size and returns an `f32` logical font size. The resolved
logical font size is then written into the `Attributes` passed to Cosmic
Text by `TextPipeline::update_buffer`.
Any text implementation using `bevy_text` must now provide viewport and
rem base values when calling `TextPipeline::update_buffer` or
`create_measure`.
`Text2d` uses the size of the primary window to resolve viewport values
(or `Vec2::splat(1000)` if no primary window is found). This is a
deliberate compromise, a single `Text2d` can be rendered to multiple
viewports using `RenderLayers`, so it's difficult to find a rule for
which viewport size should be chosen.
### Change detection
`ComputedTextBlock` has two new fields: `uses_viewport_sizes` and
`uses_rem_sizes`, which are set to true in `TextPipeline::update_buffer`
iff any text section in the block uses viewport or rem font sizes,
respectively.
The `ComputedTextBlock::needs_rerender` method has been modified to take
take two bool parameters:
```rust
pub fn needs_rerender(
&self,
is_viewport_size_changed: bool,
is_rem_size_changed: bool,
) -> bool {
self.needs_rerender
|| (is_viewport_size_changed && self.uses_viewport_sizes)
|| (is_rem_size_changed && self.uses_rem_sizes)
}
```
This ensures that text reupdates will also be scheduled if one of the text section's uses a viewport font size and the local viewport size changed, or if one of the text section's uses a rem font size and the rem size changed.
#### Limitations
There are some limitations because we don't have any sort of font style inheritance yet:
* "rem" units aren't proper rem units, and just based on the value of a resource.
* "em" units are resolved based on inherited font size, so can't be implemented without inheritance support.
#### Notes
* This PR is quite small and not very technical. Reviewers don't need to be especially familiar with `bevy_text`. Most of the changes are to the examples.
* We could consider using `Val` instead of `FontSize`, then we could use `Val`'s constructor functions which would be much nicer, but some variants might not have sensible interpretations in both UI and Text2d contexts. Also we'd have to make `Val` accessible to `bevy_text`.
## Testing
The changes to the text systems are relatively trivial and easy to understand. I already added a minor change to the `text` example to use `Vh` font size for the "hello bevy" text in the bottom right corner. If you change the size of the window, you should see the text change size in response. The text initially flickers before it updates because of some unrelated asset/image changes that mean that font textures aren't ready until the frame after the text update that changes the font size.
Most of the example migrations were automated using regular expressions, and there are bound to be mistakes in those changes. It's infeasible to check every single example thoroughly, but it's early enough in the release cycle that I don't think we should be too worried if a few bugs slip in.
---------
Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
# Objective
- Fixes#22644
## Solution
- Create new subdirectories for categorization, and update the paths in
`Cargo.toml` and `README`.
## Testing
- `cargo check --example *`