Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NoWrap Text feature #8947

Merged
merged 14 commits into from
Jun 26, 2023
16 changes: 15 additions & 1 deletion crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ impl Text {
self.alignment = alignment;
self
}

/// Returns this [`Text`] with soft wrapping disabled.
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
pub const fn with_no_wrap(mut self) -> Self {
self.linebreak_behavior = BreakLineOn::NoWrap;
self
}
}

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

impl From<BreakLineOn> for glyph_brush_layout::BuiltInLineBreaker {
fn from(val: BreakLineOn) -> Self {
match val {
BreakLineOn::WordBoundary => glyph_brush_layout::BuiltInLineBreaker::UnicodeLineBreaker,
// If `NoWrap` is set the choice of `BuiltInLineBreaker` doesn't matter as the text is given unbounded width and soft wrapping will never occur.
// But `NoWrap` does not disable hard breaks where a [`Text`] contains a newline character.
BreakLineOn::WordBoundary | BreakLineOn::NoWrap => {
glyph_brush_layout::BuiltInLineBreaker::UnicodeLineBreaker
}
BreakLineOn::AnyCharacter => glyph_brush_layout::BuiltInLineBreaker::AnyCharLineBreaker,
}
}
Expand Down
10 changes: 7 additions & 3 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use bevy_utils::HashSet;
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};

use crate::{
Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError, TextLayoutInfo,
TextPipeline, TextSettings, YAxisOrientation,
BreakLineOn, Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError,
TextLayoutInfo, TextPipeline, TextSettings, YAxisOrientation,
};

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

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ui/src/measurement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub trait Measure: Send + Sync + 'static {
/// always returns the same size.
#[derive(Default, Clone)]
pub struct FixedMeasure {
size: Vec2,
pub size: Vec2,
}

impl Measure for FixedMeasure {
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use bevy_render::{
};
use bevy_sprite::TextureAtlas;
#[cfg(feature = "bevy_text")]
use bevy_text::{Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle};
use bevy_text::{BreakLineOn, Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle};
use bevy_transform::prelude::{GlobalTransform, Transform};

/// The basic UI node
Expand Down Expand Up @@ -256,6 +256,13 @@ impl TextBundle {
self.background_color = BackgroundColor(color);
self
}

/// Returns this [`TextBundle`] with soft wrapping disabled.
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
pub const fn with_no_wrap(mut self) -> Self {
self.text.linebreak_behavior = BreakLineOn::NoWrap;
self
}
}

/// A UI node that is a button
Expand Down
21 changes: 16 additions & 5 deletions crates/bevy_ui/src/widget/text.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{ContentSize, Measure, Node, UiScale};
use crate::{ContentSize, FixedMeasure, Measure, Node, UiScale};
use bevy_asset::Assets;
use bevy_ecs::{
prelude::{Component, DetectChanges},
Expand All @@ -12,8 +12,8 @@ use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFrom
use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas;
use bevy_text::{
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextMeasureInfo,
TextPipeline, TextSettings, YAxisOrientation,
BreakLineOn, Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo,
TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
};
use bevy_window::{PrimaryWindow, Window};
use taffy::style::AvailableSpace;
Expand Down Expand Up @@ -91,7 +91,13 @@ fn create_text_measure(
text.linebreak_behavior,
) {
Ok(measure) => {
content_size.set(TextMeasure { info: measure });
if text.linebreak_behavior == BreakLineOn::NoWrap {
content_size.set(FixedMeasure {
size: measure.max_width_content_size,
});
} else {
content_size.set(TextMeasure { info: measure });
}

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

match text_pipeline.queue_text(
fonts,
Expand Down
8 changes: 6 additions & 2 deletions examples/ui/text_wrap_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
})
.id();

for linebreak_behavior in [BreakLineOn::AnyCharacter, BreakLineOn::WordBoundary] {
for linebreak_behavior in [
BreakLineOn::AnyCharacter,
BreakLineOn::WordBoundary,
BreakLineOn::NoWrap,
] {
let row_id = commands
.spawn(NodeBundle {
style: Style {
Expand Down Expand Up @@ -66,7 +70,7 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
flex_direction: FlexDirection::Column,
width: Val::Percent(16.),
height: Val::Percent(95.),
overflow: Overflow::clip(),
overflow: Overflow::clip_x(),
..Default::default()
},
background_color: Color::rgb(0.5, c, 1.0 - c).into(),
Expand Down