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

[Merged by Bors] - Allow users of Text/TextBundle to choose from glyph_brush_layout's BuiltInLineBreaker options. #7283

Closed
wants to merge 7 commits into from
10 changes: 8 additions & 2 deletions crates/bevy_text/src/glyph_brush.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas;
use bevy_utils::tracing::warn;
use glyph_brush_layout::{
FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, SectionText, ToSectionText,
BuiltInLineBreaker, FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph,
SectionText, ToSectionText,
};

use crate::{
error::TextError, Font, FontAtlasSet, FontAtlasWarning, GlyphAtlasInfo, TextAlignment,
TextSettings, YAxisOrientation,
TextLineBreakBehaviour, TextSettings, YAxisOrientation,
};

pub struct GlyphBrush {
Expand All @@ -35,13 +36,18 @@ impl GlyphBrush {
sections: &[S],
bounds: Vec2,
text_alignment: TextAlignment,
linebreak_behaviour: TextLineBreakBehaviour,
) -> Result<Vec<SectionGlyph>, TextError> {
let geom = SectionGeometry {
bounds: (bounds.x, bounds.y),
..Default::default()
};

let lbb: BuiltInLineBreaker = linebreak_behaviour.into();

let section_glyphs = Layout::default()
.h_align(text_alignment.into())
.line_breaker(lbb)
.calculate_glyphs(&self.fonts, &geom, sections);
Ok(section_glyphs)
}
Expand Down
10 changes: 6 additions & 4 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use glyph_brush_layout::{FontId, SectionText};

use crate::{
error::TextError, glyph_brush::GlyphBrush, scale_value, Font, FontAtlasSet, FontAtlasWarning,
PositionedGlyph, TextAlignment, TextSection, TextSettings, YAxisOrientation,
PositionedGlyph, TextAlignment, TextLineBreakBehaviour, TextSection, TextSettings,
YAxisOrientation,
};

#[derive(Default, Resource)]
Expand Down Expand Up @@ -45,6 +46,7 @@ impl TextPipeline {
sections: &[TextSection],
scale_factor: f64,
text_alignment: TextAlignment,
linebreak_behaviour: TextLineBreakBehaviour,
bounds: Vec2,
font_atlas_set_storage: &mut Assets<FontAtlasSet>,
texture_atlases: &mut Assets<TextureAtlas>,
Expand Down Expand Up @@ -75,9 +77,9 @@ impl TextPipeline {
})
.collect::<Result<Vec<_>, _>>()?;

let section_glyphs = self
.brush
.compute_glyphs(&sections, bounds, text_alignment)?;
let section_glyphs =
self.brush
.compute_glyphs(&sections, bounds, text_alignment, linebreak_behaviour)?;

if section_glyphs.is_empty() {
return Ok(TextLayoutInfo::default());
Expand Down
36 changes: 36 additions & 0 deletions crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ pub struct Text {
/// The text's internal alignment.
/// Should not affect its position within a container.
pub alignment: TextAlignment,
/// How the text should linebreak when running out of the bounds determined by max_size
pub linebreak_behaviour: TextLineBreakBehaviour,
}

impl Default for Text {
fn default() -> Self {
Self {
sections: Default::default(),
alignment: TextAlignment::Left,
linebreak_behaviour: TextLineBreakBehaviour::Unicode,
}
}
}
Expand Down Expand Up @@ -103,6 +106,14 @@ impl Text {
self.alignment = alignment;
self
}

pub const fn with_linebreak_behaviour(
Molot2032 marked this conversation as resolved.
Show resolved Hide resolved
mut self,
linebreak_behaviour: TextLineBreakBehaviour,
) -> Self {
self.linebreak_behaviour = linebreak_behaviour;
self
}
}

#[derive(Debug, Default, Clone, FromReflect, Reflect)]
Expand Down Expand Up @@ -170,3 +181,28 @@ impl Default for TextStyle {
}
}
}

/// Determines how lines will be broken when preventing text from running out of bounds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[reflect(Serialize, Deserialize)]
pub enum TextLineBreakBehaviour {
Molot2032 marked this conversation as resolved.
Show resolved Hide resolved
/// Lines will be broken up at the nearest word boundary, usually at a space.<br/>
/// This behaviour suits most cases, as it keeps words intact. Aims to implement the Unicode line breaking algorithm.
Unicode,
Molot2032 marked this conversation as resolved.
Show resolved Hide resolved
/// Lines will be broken without discrimination at the first character that runs out of bounds.<br/>
/// This is closer to the behaviour one might expect from a terminal.
AnyCharacter,
}

impl From<TextLineBreakBehaviour> for glyph_brush_layout::BuiltInLineBreaker {
fn from(val: TextLineBreakBehaviour) -> Self {
match val {
TextLineBreakBehaviour::Unicode => {
glyph_brush_layout::BuiltInLineBreaker::UnicodeLineBreaker
}
TextLineBreakBehaviour::AnyCharacter => {
glyph_brush_layout::BuiltInLineBreaker::AnyCharLineBreaker
}
}
}
}
1 change: 1 addition & 0 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ pub fn update_text2d_layout(
&text.sections,
scale_factor,
text.alignment,
text.linebreak_behaviour,
text_bounds,
&mut font_atlas_set_storage,
&mut texture_atlases,
Expand Down
11 changes: 10 additions & 1 deletion crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use bevy_render::{
prelude::{Color, ComputedVisibility},
view::Visibility,
};
use bevy_text::{Text, TextAlignment, TextSection, TextStyle};
use bevy_text::{Text, TextAlignment, TextLineBreakBehaviour, TextSection, TextStyle};
use bevy_transform::prelude::{GlobalTransform, Transform};

/// The basic UI node
Expand Down Expand Up @@ -153,6 +153,15 @@ impl TextBundle {
self
}

/// Returns this [`TextBundle`] with a new [`TextLineBreakBehaviour`] on [`Text`].
pub const fn with_linebreak_behaviour(
mut self,
linebreak_behaviour: TextLineBreakBehaviour,
) -> Self {
self.text.linebreak_behaviour = linebreak_behaviour;
self
}

/// Returns this [`TextBundle`] with a new [`Style`].
pub const fn with_style(mut self, style: Style) -> Self {
self.style = style;
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ui/src/widget/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub fn text_system(
&text.sections,
scale_factor,
text.alignment,
text.linebreak_behaviour,
node_size,
&mut font_atlas_set_storage,
&mut texture_atlases,
Expand Down
52 changes: 47 additions & 5 deletions examples/2d/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
//! For an example on how to render text as part of a user interface, independent from the world
//! viewport, you may want to look at `2d/contributors.rs` or `ui/text.rs`.

use bevy::{prelude::*, text::Text2dBounds};
use bevy::{
prelude::*,
text::{Text2dBounds, TextLineBreakBehaviour},
};

fn main() {
App::new()
Expand All @@ -29,7 +32,7 @@ struct AnimateScale;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
let text_style = TextStyle {
font,
font: font.clone(),
font_size: 60.0,
color: Color::WHITE,
};
Expand All @@ -56,12 +59,17 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Demonstrate changing scale
commands.spawn((
Text2dBundle {
text: Text::from_section("scale", text_style.clone()).with_alignment(text_alignment),
text: Text::from_section("scale", text_style).with_alignment(text_alignment),
..default()
},
AnimateScale,
));
// Demonstrate text wrapping
let slightly_smaller_text_style = TextStyle {
font,
font_size: 42.0,
color: Color::WHITE,
};
let box_size = Vec2::new(300.0, 200.0);
let box_position = Vec2::new(0.0, -250.0);
commands
Expand All @@ -76,8 +84,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
})
.with_children(|builder| {
builder.spawn(Text2dBundle {
text: Text::from_section("this text wraps in the box", text_style)
.with_alignment(TextAlignment::Left),
text: Text::from_section(
"this text wraps in the box\n(Unicode linebreaks)",
slightly_smaller_text_style.clone(),
)
.with_alignment(TextAlignment::Left)
.with_linebreak_behaviour(TextLineBreakBehaviour::Unicode),
text_2d_bounds: Text2dBounds {
// Wrap text in the rectangle
size: box_size,
Expand All @@ -87,6 +99,36 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
});
});

let other_box_size = Vec2::new(300.0, 200.0);
let other_box_position = Vec2::new(320.0, -250.0);
commands
.spawn(SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.20, 0.3, 0.70),
custom_size: Some(Vec2::new(other_box_size.x, other_box_size.y)),
..default()
},
transform: Transform::from_translation(other_box_position.extend(0.0)),
..default()
})
.with_children(|builder| {
builder.spawn(Text2dBundle {
text: Text::from_section(
"this text wraps in the box\n(AnyCharacter linebreaks)",
slightly_smaller_text_style,
)
.with_alignment(TextAlignment::Left)
.with_linebreak_behaviour(TextLineBreakBehaviour::AnyCharacter),
text_2d_bounds: Text2dBounds {
// Wrap text in the rectangle
size: other_box_size,
},
// ensure the text is drawn on top of the box
transform: Transform::from_translation(Vec3::Z),
..default()
});
});
}

fn animate_translation(
Expand Down