Skip to content

Commit aeea4b0

Browse files
authored
NoWrap Text feature (#8947)
# Objective In Bevy 10.1 and before, the only way to enable text wrapping was to set a local `Val::Px` width constraint on the text node itself. `Val::Percent` constraints and constraints on the text node's ancestors did nothing. #7779 fixed those problems. But perversely displaying unwrapped text is really difficult now, and requires users to nest each `TextBundle` in a `NodeBundle` and apply `min_width` and `max_width` constraints. Some constructions may even need more than one layer of nesting. I've seen several people already who have really struggled with this when porting their projects to main in advance of 0.11. ## Solution Add a `NoWrap` variant to the `BreakLineOn` enum. If `NoWrap` is set, ignore any constraints on the width for the text and call `TextPipeline::queue_text` with a width bound of `f32::INFINITY`. --- ## Changelog * Added a `NoWrap` variant to the `BreakLineOn` enum. * If `NoWrap` is set, any constraints on the width for the text are ignored and `TextPipeline::queue_text` is called with a width bound of `f32::INFINITY`. * Changed the `size` field of `FixedMeasure` to `pub`. This shouldn't have been private, it was always intended to have `pub` visibility. * Added a `with_no_wrap` method to `TextBundle`. ## Migration Guide `bevy_text::text::BreakLineOn` has a new variant `NoWrap` that disables text wrapping for the `Text`. Text wrapping can also be disabled using the `with_no_wrap` method of `TextBundle`.
1 parent 4b1a502 commit aeea4b0

File tree

6 files changed

+53
-13
lines changed

6 files changed

+53
-13
lines changed

crates/bevy_text/src/text.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ impl Text {
106106
self.alignment = alignment;
107107
self
108108
}
109+
110+
/// Returns this [`Text`] with soft wrapping disabled.
111+
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
112+
pub const fn with_no_wrap(mut self) -> Self {
113+
self.linebreak_behavior = BreakLineOn::NoWrap;
114+
self
115+
}
109116
}
110117

111118
#[derive(Debug, Default, Clone, FromReflect, Reflect)]
@@ -186,12 +193,19 @@ pub enum BreakLineOn {
186193
/// This is closer to the behavior one might expect from text in a terminal.
187194
/// However it may lead to words being broken up across linebreaks.
188195
AnyCharacter,
196+
/// No soft wrapping, where text is automatically broken up into separate lines when it overflows a boundary, will ever occur.
197+
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, is still enabled.
198+
NoWrap,
189199
}
190200

191201
impl From<BreakLineOn> for glyph_brush_layout::BuiltInLineBreaker {
192202
fn from(val: BreakLineOn) -> Self {
193203
match val {
194-
BreakLineOn::WordBoundary => glyph_brush_layout::BuiltInLineBreaker::UnicodeLineBreaker,
204+
// If `NoWrap` is set the choice of `BuiltInLineBreaker` doesn't matter as the text is given unbounded width and soft wrapping will never occur.
205+
// But `NoWrap` does not disable hard breaks where a [`Text`] contains a newline character.
206+
BreakLineOn::WordBoundary | BreakLineOn::NoWrap => {
207+
glyph_brush_layout::BuiltInLineBreaker::UnicodeLineBreaker
208+
}
195209
BreakLineOn::AnyCharacter => glyph_brush_layout::BuiltInLineBreaker::AnyCharLineBreaker,
196210
}
197211
}

crates/bevy_text/src/text2d.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use bevy_utils::HashSet;
2323
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
2424

2525
use crate::{
26-
Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError, TextLayoutInfo,
27-
TextPipeline, TextSettings, YAxisOrientation,
26+
BreakLineOn, Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError,
27+
TextLayoutInfo, TextPipeline, TextSettings, YAxisOrientation,
2828
};
2929

3030
/// The maximum width and height of text. The text will wrap according to the specified size.
@@ -174,7 +174,11 @@ pub fn update_text2d_layout(
174174
for (entity, text, bounds, mut text_layout_info) in &mut text_query {
175175
if factor_changed || text.is_changed() || bounds.is_changed() || queue.remove(&entity) {
176176
let text_bounds = Vec2::new(
177-
scale_value(bounds.size.x, scale_factor),
177+
if text.linebreak_behavior == BreakLineOn::NoWrap {
178+
f32::INFINITY
179+
} else {
180+
scale_value(bounds.size.x, scale_factor)
181+
},
178182
scale_value(bounds.size.y, scale_factor),
179183
);
180184

crates/bevy_ui/src/measurement.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub trait Measure: Send + Sync + 'static {
2828
/// always returns the same size.
2929
#[derive(Default, Clone)]
3030
pub struct FixedMeasure {
31-
size: Vec2,
31+
pub size: Vec2,
3232
}
3333

3434
impl Measure for FixedMeasure {

crates/bevy_ui/src/node_bundles.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use bevy_render::{
1313
};
1414
use bevy_sprite::TextureAtlas;
1515
#[cfg(feature = "bevy_text")]
16-
use bevy_text::{Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle};
16+
use bevy_text::{BreakLineOn, Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle};
1717
use bevy_transform::prelude::{GlobalTransform, Transform};
1818

1919
/// The basic UI node
@@ -256,6 +256,13 @@ impl TextBundle {
256256
self.background_color = BackgroundColor(color);
257257
self
258258
}
259+
260+
/// Returns this [`TextBundle`] with soft wrapping disabled.
261+
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
262+
pub const fn with_no_wrap(mut self) -> Self {
263+
self.text.linebreak_behavior = BreakLineOn::NoWrap;
264+
self
265+
}
259266
}
260267

261268
/// A UI node that is a button

crates/bevy_ui/src/widget/text.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{ContentSize, Measure, Node, UiScale};
1+
use crate::{ContentSize, FixedMeasure, Measure, Node, UiScale};
22
use bevy_asset::Assets;
33
use bevy_ecs::{
44
prelude::{Component, DetectChanges},
@@ -12,8 +12,8 @@ use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFrom
1212
use bevy_render::texture::Image;
1313
use bevy_sprite::TextureAtlas;
1414
use bevy_text::{
15-
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextMeasureInfo,
16-
TextPipeline, TextSettings, YAxisOrientation,
15+
BreakLineOn, Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo,
16+
TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
1717
};
1818
use bevy_window::{PrimaryWindow, Window};
1919
use taffy::style::AvailableSpace;
@@ -91,7 +91,13 @@ fn create_text_measure(
9191
text.linebreak_behavior,
9292
) {
9393
Ok(measure) => {
94-
content_size.set(TextMeasure { info: measure });
94+
if text.linebreak_behavior == BreakLineOn::NoWrap {
95+
content_size.set(FixedMeasure {
96+
size: measure.max_width_content_size,
97+
});
98+
} else {
99+
content_size.set(TextMeasure { info: measure });
100+
}
95101

96102
// Text measure func created succesfully, so set `TextFlags` to schedule a recompute
97103
text_flags.needs_new_measure_func = false;
@@ -174,7 +180,12 @@ fn queue_text(
174180
) {
175181
// Skip the text node if it is waiting for a new measure func
176182
if !text_flags.needs_new_measure_func {
177-
let physical_node_size = node.physical_size(scale_factor);
183+
let physical_node_size = if text.linebreak_behavior == BreakLineOn::NoWrap {
184+
// With `NoWrap` set, no constraints are placed on the width of the text.
185+
Vec2::splat(f32::INFINITY)
186+
} else {
187+
node.physical_size(scale_factor)
188+
};
178189

179190
match text_pipeline.queue_text(
180191
fonts,

examples/ui/text_wrap_debug.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
3333
})
3434
.id();
3535

36-
for linebreak_behavior in [BreakLineOn::AnyCharacter, BreakLineOn::WordBoundary] {
36+
for linebreak_behavior in [
37+
BreakLineOn::AnyCharacter,
38+
BreakLineOn::WordBoundary,
39+
BreakLineOn::NoWrap,
40+
] {
3741
let row_id = commands
3842
.spawn(NodeBundle {
3943
style: Style {
@@ -66,7 +70,7 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
6670
flex_direction: FlexDirection::Column,
6771
width: Val::Percent(16.),
6872
height: Val::Percent(95.),
69-
overflow: Overflow::clip(),
73+
overflow: Overflow::clip_x(),
7074
..Default::default()
7175
},
7276
background_color: Color::rgb(0.5, c, 1.0 - c).into(),

0 commit comments

Comments
 (0)