Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2edb3b2
Initial port to parley
alice-i-cecile Feb 11, 2026
2a0db90
Added `TextCursorStyle` component.
ickshonpe Feb 25, 2026
3853701
Added cursor and selection rects to TextLayoutInfo
ickshonpe Feb 25, 2026
ec07639
Added cursor and selection rects rendering
ickshonpe Feb 25, 2026
0057f73
Move cursor rendering to own file
Zeophlite Mar 9, 2026
8b45b19
editable_text.rs -> text_editable.rs
Zeophlite Mar 9, 2026
e7a44e5
Move TextEdit to own file
Zeophlite Mar 9, 2026
ebc1d61
Move editing.rs to cursor.rs
Zeophlite Mar 9, 2026
04c411b
WIP Input Text
Zeophlite Mar 9, 2026
8e06897
CI
Zeophlite Mar 9, 2026
0a4c802
CI
Zeophlite Mar 9, 2026
1a8b3cc
Clippy
Zeophlite Mar 9, 2026
85fd548
Apply suggestions from code review
Zeophlite Mar 10, 2026
8a8cc57
Further feedback
Zeophlite Mar 10, 2026
ca33995
more review
Zeophlite Mar 10, 2026
f2195de
Merge remote-tracking branch 'origin/main' into parley-text-input2
Zeophlite Mar 10, 2026
e66f0b5
default
Zeophlite Mar 10, 2026
5b3b8ec
Support changing font size
Zeophlite Mar 10, 2026
4fe43dc
Enable selection
Zeophlite Mar 10, 2026
3bb5c74
Only need remeasure on font size change
Zeophlite Mar 11, 2026
c93be22
CI
Zeophlite Mar 12, 2026
f6610b7
Fix rects
Zeophlite Mar 12, 2026
3a50a41
Merge remote-tracking branch 'origin/main' into parley-text-input2
Zeophlite Mar 12, 2026
ae3cc93
Try without fontconfig
Zeophlite Mar 12, 2026
19459c3
Partial parley-input
ickshonpe Mar 14, 2026
a1fe756
Remove unneeded
Zeophlite Mar 15, 2026
38c3363
Fixes
Zeophlite Mar 15, 2026
cd1c713
CI
Zeophlite Mar 15, 2026
3d62f8a
Fix up cursor width
Zeophlite Mar 15, 2026
ad6cde0
Clean up
Zeophlite Mar 15, 2026
09eed51
Apply suggestions from code review
Zeophlite Mar 17, 2026
c1449b4
Review
Zeophlite Mar 17, 2026
6cc5689
Merge remote-tracking branch 'origin/main' into parley-text-input2
Zeophlite Mar 17, 2026
c59943d
Use TextBrush now
Zeophlite Mar 17, 2026
49d7294
cat walked over my keyboard
Zeophlite Mar 17, 2026
dd060e6
Fix outdated instructions in example
alice-i-cecile Mar 17, 2026
448b0e0
Apply suggestions from code review
Zeophlite Mar 19, 2026
b6c2576
Cleanup
Zeophlite Mar 19, 2026
443161c
Merge remote-tracking branch 'origin/main' into parley-text-input2
Zeophlite Mar 19, 2026
577b694
Change to observer
Zeophlite Mar 19, 2026
227e757
Doc
Zeophlite Mar 19, 2026
24a0298
Rm extract_text_editable from bevy_ui_render
Zeophlite Mar 20, 2026
fd4872d
CI
Zeophlite Mar 20, 2026
cd799ff
text_system Without<EditableText>
Zeophlite Mar 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,17 @@ description = "Renders text to multiple windows with different scale factors usi
category = "2D Rendering"
wasm = true

[[example]]
name = "editable_text"
path = "examples/ui/text/editable_text.rs"
doc-scrape-examples = true

[package.metadata.example.editable_text]
name = "Editable Text"
description = "Demonstrates a simple, unstyled text input widget"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "texture_atlas"
path = "examples/2d/texture_atlas.rs"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl Plugin for SpritePlugin {
.chain()
.after(detect_text_needs_rerender)
.after(bevy_text::load_font_assets_into_font_collection)
.after(bevy_text::apply_text_edits)
.after(bevy_app::AnimationSystems)
.before(bevy_asset::AssetEventSystems),
)
Expand Down
28 changes: 28 additions & 0 deletions crates/bevy_text/src/cursor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use bevy_color::{
palettes::css::{GREEN, RED},
Alpha as _, Color,
};
use bevy_ecs::component::Component;

/// Controls text cursor appearance.
///
/// When this component on the same entity as an [`EditableText`](`crate::EditableText`) ,
/// and the [`UiRenderPlugin`](https://docs.rs/bevy/latest/bevy/ui_render/struct.UiRenderPlugin.html)
/// is active, a simple rectangle will be drawn for the cursor.
/// This is an optional component, to allow for stylistic cursors.
#[derive(Component, Clone, Copy, Debug, PartialEq)]
pub struct TextCursorStyle {
Comment thread
Zeophlite marked this conversation as resolved.
/// Color of the cursor
pub color: Color,
/// Background color of selected text
pub selection_color: Color,
}

impl Default for TextCursorStyle {
fn default() -> Self {
Self {
color: RED.into(),
selection_color: Color::from(GREEN).with_alpha(0.5),
}
}
}
13 changes: 12 additions & 1 deletion crates/bevy_text/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
extern crate alloc;

mod bounds;
mod cursor;
mod error;
mod font;
mod font_atlas;
Expand All @@ -42,8 +43,11 @@ mod parley_context;
mod pipeline;
mod text;
mod text_access;
mod text_edit;
mod text_editable;

pub use bounds::*;
pub use cursor::*;
pub use error::*;
pub use font::*;
pub use font_atlas::*;
Expand All @@ -54,6 +58,8 @@ pub use parley_context::*;
pub use pipeline::*;
pub use text::*;
pub use text_access::*;
pub use text_edit::*;
pub use text_editable::*;

/// The text prelude.
///
Expand Down Expand Up @@ -86,6 +92,10 @@ pub struct TextPlugin;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub struct Text2dUpdateSystems;

/// System set where [`EditableText::pending_edits`] are applied.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub struct EditableTextSystems;

impl Plugin for TextPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<Font>()
Expand All @@ -105,7 +115,8 @@ impl Plugin for TextPlugin {
)
.chain(),
)
.add_systems(Last, trim_source_cache);
.add_systems(Last, trim_source_cache)
.add_systems(PostUpdate, apply_text_edits.in_set(EditableTextSystems));

#[cfg(feature = "default_font")]
{
Expand Down
16 changes: 12 additions & 4 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl TextPipeline {
);
builder.push(StyleProperty::FontSize(section.font_size), range.clone());
builder.push(
StyleProperty::LineHeight(section.line_height.eval(section.font_size)),
StyleProperty::LineHeight(section.line_height.eval()),
range.clone(),
);
builder.push(
Expand Down Expand Up @@ -410,18 +410,20 @@ impl TextPipeline {
}

layout_info.size = Vec2::new(layout.full_width(), layout.height()).ceil();

Ok(())
}
}

fn resolve_font_source<'a>(
/// Resolve a [`FontSource`], producing a [`FontFamily`], by looking it up in the [`Assets<Font>`] collection.
pub fn resolve_font_source<'a>(
font: &'a FontSource,
fonts: &'a Assets<Font>,
fonts: &Assets<Font>,
) -> Result<FontFamily<'a>, TextError> {
Ok(match font {
FontSource::Handle(handle) => {
let font = fonts.get(handle.id()).ok_or(TextError::NoSuchFont)?;
FontFamily::Named(Cow::Borrowed(font.family_name.as_str()))
FontFamily::Named(Cow::Owned(font.family_name.as_str().to_owned()))
}
FontSource::Family(family) => FontFamily::Named(Cow::Borrowed(family.as_str())),
FontSource::Serif => FontFamily::Generic(parley::GenericFamily::Serif),
Expand Down Expand Up @@ -459,6 +461,10 @@ pub struct TextLayoutInfo {
pub run_geometry: Vec<RunGeometry>,
/// The glyphs resulting size
pub size: Vec2,
/// Cursor size and position for editing
pub cursor: Option<Rect>,
/// Selection rects
pub selection_rects: Vec<Rect>,
}

impl TextLayoutInfo {
Expand All @@ -468,6 +474,8 @@ impl TextLayoutInfo {
self.glyphs.clear();
self.run_geometry.clear();
self.size = Vec2::ZERO;
self.cursor = None;
self.selection_rects.clear();
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,8 @@ pub enum LineHeight {
}

impl LineHeight {
pub(crate) fn eval(self, _font_size: f32) -> parley::LineHeight {
/// eval a line height
pub fn eval(self) -> parley::LineHeight {
match self {
LineHeight::Px(px) => parley::LineHeight::Absolute(px),
LineHeight::RelativeToFont(scale) => parley::LineHeight::FontSizeRelative(scale),
Expand Down
65 changes: 65 additions & 0 deletions crates/bevy_text/src/text_edit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use bevy_reflect::Reflect;
use parley::PlainEditorDriver;
use smol_str::SmolStr;

use crate::TextBrush;

/// Deferred text input edit and navigation actions applied by the `apply_text_edits` system.
#[derive(Debug, Clone, PartialEq, Eq, Reflect)]
pub enum TextEdit {
/// Insert a character at the cursor. If there is a selection, replaces the selection with the character instead.
///
/// Typically generated in response to keyboard text input events.
///
/// This is intended to insert a single Unicode grapheme cluster, such as a letter, digit, punctuation mark, or emoji.
Insert(SmolStr),
/// Delete the character behind the cursor.
/// If there is a selection, deletes the selection instead.
///
/// Typically generated in response to the backspace key.
///
/// This operation removes an entire Unicode grapheme cluster, which may consist of multiple bytes,
/// shifting the cursor position accordingly.
Backspace,
/// Delete the character at the cursor.
/// If there is a selection, deletes the selection instead.
///
/// Typically generated in response to the delete key.
///
/// This operation removes an entire Unicode grapheme cluster, which may consist of multiple bytes,
/// shifting the cursor position accordingly.
Delete,
/// Moves the cursor by one position to the right.
///
/// Typically generated in response to the right key.
MoveCursorRight,
/// Moves the cursor by one position to the left.
///
/// Typically generated in response to the left key.
MoveCursorLeft,
/// Move selection on to the right.
///
/// Typically generated in response to shift and the right key.
SelectRight,
/// Move selection on to the left.
///
/// Typically generated in response to shift and the left key.
SelectLeft,
}

/// Takes a `TextEdit` and applies to `PlainEditorDriver`
pub fn apply_edit<'a>(
edit: TextEdit,
mut driver: PlainEditorDriver<'a, TextBrush>,
) -> PlainEditorDriver<'a, TextBrush> {
match edit {
TextEdit::Insert(str) => driver.insert_or_replace_selection(&str),
TextEdit::Backspace => driver.backdelete(),
TextEdit::Delete => driver.delete(),
TextEdit::MoveCursorRight => driver.move_right(),
TextEdit::MoveCursorLeft => driver.move_left(),
TextEdit::SelectRight => driver.select_right(),
TextEdit::SelectLeft => driver.select_left(),
}
driver
}
Loading