Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
89c1796
New `multiple_text_inputs` example.
ickshonpe Mar 30, 2026
0923b02
fixed spelling mistake
ickshonpe Mar 30, 2026
c9ae3cd
Merge branch 'main' into multiple_text_inputs_example
ickshonpe Mar 31, 2026
5483182
Added more comments for example
ickshonpe Mar 31, 2026
0b8e387
Explain why `EditableText` returns a `SplitString` in the comments fo…
ickshonpe Mar 31, 2026
656e976
Added labels for columns
ickshonpe Mar 31, 2026
ecc0c6c
Improved layout and dim border colors when focus lost.
ickshonpe Mar 31, 2026
422b718
fixed comment for the synchronise_output_text system
ickshonpe Mar 31, 2026
8d167b6
Merge branch 'main' into multiple_text_inputs_example
ickshonpe Mar 31, 2026
f2cc729
Fixed typo
ickshonpe Mar 31, 2026
b0fbdad
Merge branch 'multiple_text_inputs_example' of https://github.com/ick…
ickshonpe Mar 31, 2026
fd67ab0
Another typo.
ickshonpe Mar 31, 2026
6081fe7
another typo
ickshonpe Mar 31, 2026
56ce0ab
Added `TextScroll` component, newtypes a Vec2 representing the offset…
ickshonpe Mar 31, 2026
0ddee8a
apply the offset from TextScroll during rendering
ickshonpe Mar 31, 2026
12c92aa
modify clip rect if TextScroll is present in text extraction
ickshonpe Mar 31, 2026
ed2eb7e
update pointer event handlers for TextScroll
ickshonpe Mar 31, 2026
e51b411
clip cursor with TextScroll
ickshonpe Mar 31, 2026
15c93af
Fixed text_clip positions
ickshonpe Mar 31, 2026
7d9e1e2
Merge branch 'multiple_text_inputs_example' into editable-text-scrolling
ickshonpe Mar 31, 2026
55d23a8
added multiline_text_input example
ickshonpe Mar 31, 2026
1d289a2
query for TextLayout in editable_text_system
ickshonpe Mar 31, 2026
747fbbf
Set word wrap settings in editable_text_system
ickshonpe Mar 31, 2026
f5add74
Merge branch 'main' into editable-text-scrolling
ickshonpe Apr 2, 2026
87b58a0
added an `allow_newlines` flag to `EditableText`, text widget only ac…
ickshonpe Apr 2, 2026
9b2e5ae
Added `scroll_editable_text` system
ickshonpe Apr 2, 2026
1ff08a2
implemented basic `scroll_editable_text` system
ickshonpe Apr 2, 2026
c3648a3
Merge branch 'main' into editable-text-scrolling
ickshonpe Apr 3, 2026
327abf5
Merge branch 'main' into editable-text-scrolling
ickshonpe Apr 3, 2026
51312ce
cargo run -p build-templated-pages -- update examples
ickshonpe Apr 3, 2026
a312809
Merge branch 'editable-text-scrolling' of https://github.com/ickshonp…
ickshonpe Apr 3, 2026
615ecd6
Removed add_plugins for add_plugins for EditableTextInputPlugin and I…
ickshonpe Apr 3, 2026
c62d0cd
Merge branch 'main' into editable-text-scrolling
ickshonpe Apr 5, 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 @@ -1009,6 +1009,17 @@ description = "Demonstrates a simple, unstyled text input widget"
category = "UI (User Interface)"
wasm = true

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

[package.metadata.example.multiline_text_input]
name = "Multiline Text Input"
description = "Demonstrates a single multiline EditableText widget"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "multiple_text_inputs"
path = "examples/ui/text/multiple_text_inputs.rs"
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_text/src/text_editable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ pub struct EditableText {
pub max_characters: Option<usize>,
/// Sets the input’s height in number of visible lines.
pub visible_lines: Option<f32>,
/// Allow new lines
pub allow_newlines: bool,
}

impl Default for EditableText {
Expand All @@ -138,6 +140,7 @@ impl Default for EditableText {
text_edited: false,
max_characters: None,
visible_lines: Some(1.),
allow_newlines: false,
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ fn build_text_interop(app: &mut App) {
.in_set(UiSystems::Content)
.ambiguous_with(widget::update_image_content_size_system)
.ambiguous_with(widget::measure_text_system),
widget::editable_text_system
(widget::editable_text_system, widget::scroll_editable_text)
.chain()
.in_set(UiSystems::PostLayout)
.ambiguous_with(ui_stack_system)
.ambiguous_with(widget::text_system)
Expand Down
88 changes: 80 additions & 8 deletions crates/bevy_ui/src/widget/text_editable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use bevy_asset::Assets;

use bevy_ecs::{
change_detection::DetectChanges,
component::Component,
entity::Entity,
system::{Local, Query, Res, ResMut},
world::Ref,
Expand All @@ -16,14 +17,18 @@ use bevy_math::{Rect, Vec2};
use bevy_platform::hash::FixedHasher;
use bevy_text::{
add_glyph_to_atlas, get_glyph_atlas_info, resolve_font_source, EditableText, Font,
FontAtlasKey, FontAtlasSet, FontCx, FontHinting, GlyphCacheKey, LayoutCx, LineHeight,
PositionedGlyph, RemSize, RunGeometry, ScaleCx, TextBrush, TextFont, TextLayoutInfo,
FontAtlasKey, FontAtlasSet, FontCx, FontHinting, GlyphCacheKey, LayoutCx, LineBreak,
LineHeight, PositionedGlyph, RemSize, RunGeometry, ScaleCx, TextBrush, TextFont, TextLayout,
TextLayoutInfo,
};
use bevy_time::{Real, Time};
use parley::{BoundingBox, PositionedLayoutItem};
use parley::{BoundingBox, PositionedLayoutItem, StyleProperty};
use swash::FontRef;
use taffy::MaybeMath;

#[derive(Component, Clone, Copy, PartialEq, Debug, Default)]
pub struct TextScroll(pub Vec2);

struct TextInputMeasure {
height: f32,
}
Expand Down Expand Up @@ -108,6 +113,7 @@ pub fn editable_text_system(
&mut EditableText,
&mut TextLayoutInfo,
Ref<ComputedNode>,
&TextLayout,
)>,
rem_size: Res<RemSize>,
input_focus: Option<Res<InputFocus>>,
Expand All @@ -125,6 +131,7 @@ pub fn editable_text_system(
mut editable_text,
mut info,
computed_node,
text_layout,
) in input_field_query.iter_mut()
{
let Ok(font_family) = resolve_font_source(&text_font.font, fonts.as_ref()) else {
Expand All @@ -133,25 +140,48 @@ pub fn editable_text_system(

let family = font_family.into_owned();
let style_set = editable_text.editor.edit_styles();
style_set.insert(parley::StyleProperty::LineHeight(line_height.eval()));
style_set.insert(parley::StyleProperty::FontFamily(family));
style_set.insert(StyleProperty::LineHeight(line_height.eval()));
style_set.insert(StyleProperty::FontFamily(family));

let logical_viewport_size = target.logical_size();
let font_size = text_font.font_size.eval(logical_viewport_size, rem_size.0);
style_set.insert(parley::StyleProperty::FontSize(font_size));
style_set.insert(parley::StyleProperty::Brush(TextBrush::new(
style_set.insert(StyleProperty::FontSize(font_size));
style_set.insert(StyleProperty::Brush(TextBrush::new(
0,
text_font.font_smoothing,
)));

match text_layout.linebreak {
LineBreak::AnyCharacter => {
style_set.insert(StyleProperty::WordBreak(parley::WordBreak::BreakAll));
style_set.insert(StyleProperty::OverflowWrap(parley::OverflowWrap::Normal));
style_set.insert(StyleProperty::TextWrapMode(parley::TextWrapMode::Wrap));
}
LineBreak::WordOrCharacter => {
style_set.insert(StyleProperty::WordBreak(parley::WordBreak::Normal));
style_set.insert(StyleProperty::OverflowWrap(parley::OverflowWrap::Anywhere));
style_set.insert(StyleProperty::TextWrapMode(parley::TextWrapMode::Wrap));
}
LineBreak::NoWrap => {
style_set.insert(StyleProperty::WordBreak(parley::WordBreak::Normal));
style_set.insert(StyleProperty::OverflowWrap(parley::OverflowWrap::Normal));
style_set.insert(StyleProperty::TextWrapMode(parley::TextWrapMode::NoWrap));
}
LineBreak::WordBoundary => {
style_set.insert(StyleProperty::WordBreak(parley::WordBreak::Normal));
style_set.insert(StyleProperty::OverflowWrap(parley::OverflowWrap::Normal));
style_set.insert(StyleProperty::TextWrapMode(parley::TextWrapMode::Wrap));
}
}

if target.is_changed() {
editable_text.editor.set_scale(target.scale_factor());
}

if computed_node.is_changed() {
editable_text
.editor
.set_width(Some(computed_node.content_size().x));
.set_width(Some(computed_node.content_box().width()));
}

let mut driver = editable_text
Expand Down Expand Up @@ -304,3 +334,45 @@ fn bounding_box_to_rect(geom: BoundingBox) -> Rect {
},
}
}

/// Scroll editable text to keep cursor in view after edits.
pub fn scroll_editable_text(mut query: Query<(&EditableText, &mut TextScroll, &ComputedNode)>) {
for (editable_text, mut scroll, node) in query.iter_mut() {
if !editable_text.text_edited {
continue;
}

let view_size = node.content_box().size();
if view_size.cmple(Vec2::ZERO).any() {
continue;
}

let Some(cursor) = editable_text
.editor
.cursor_geometry(1.0)
.map(bounding_box_to_rect)
else {
continue;
};

let mut new_scroll = scroll.0;

if cursor.min.x < new_scroll.x {
new_scroll.x = cursor.min.x;
} else if new_scroll.x + view_size.x < cursor.max.x {
new_scroll.x = cursor.max.x - view_size.x;
}

if cursor.min.y < new_scroll.y {
new_scroll.y = cursor.min.y;
} else if new_scroll.y + view_size.y < cursor.max.y {
new_scroll.y = cursor.max.y - view_size.y;
}

new_scroll = new_scroll.max(Vec2::ZERO);

if scroll.0 != new_scroll {
scroll.0 = new_scroll;
}
}
}
Loading