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
12 changes: 9 additions & 3 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,
error::TextError, BreakLineOn, Font, FontAtlasSet, FontAtlasWarning, GlyphAtlasInfo,
TextAlignment, TextSettings, YAxisOrientation,
};

pub struct GlyphBrush {
Expand All @@ -35,13 +36,18 @@ impl GlyphBrush {
sections: &[S],
bounds: Vec2,
text_alignment: TextAlignment,
linebreak_behaviour: BreakLineOn,
) -> 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
11 changes: 6 additions & 5 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use bevy_utils::HashMap;
use glyph_brush_layout::{FontId, SectionText};

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

#[derive(Default, Resource)]
Expand Down Expand Up @@ -45,6 +45,7 @@ impl TextPipeline {
sections: &[TextSection],
scale_factor: f64,
text_alignment: TextAlignment,
linebreak_behaviour: BreakLineOn,
bounds: Vec2,
font_atlas_set_storage: &mut Assets<FontAtlasSet>,
texture_atlases: &mut Assets<TextureAtlas>,
Expand Down Expand Up @@ -75,9 +76,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
26 changes: 26 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: BreakLineOn,
}

impl Default for Text {
fn default() -> Self {
Self {
sections: Default::default(),
alignment: TextAlignment::Left,
linebreak_behaviour: BreakLineOn::WordBoundary,
}
}
}
Expand Down Expand Up @@ -170,3 +173,26 @@ 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 BreakLineOn {
/// Uses the [Unicode Line Breaking Algorithm](https://www.unicode.org/reports/tr14/).
/// Lines will be broken up at the nearest suitable word boundary, usually a space.
/// This behaviour suits most cases, as it keeps words intact across linebreaks.
WordBoundary,
/// Lines will be broken without discrimination on any character that would leave bounds.
/// This is closer to the behaviour one might expect from text in a terminal.
/// However it may lead to words being broken up across linebreaks.
AnyCharacter,
}

impl From<BreakLineOn> for glyph_brush_layout::BuiltInLineBreaker {
fn from(val: BreakLineOn) -> Self {
match val {
BreakLineOn::WordBoundary => glyph_brush_layout::BuiltInLineBreaker::UnicodeLineBreaker,
BreakLineOn::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
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
56 changes: 51 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::{BreakLineOn, Text2dBounds},
};

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,14 @@ 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 {
sections: vec![TextSection::new(
"this text wraps in the box\n(Unicode linebreaks)",
slightly_smaller_text_style.clone(),
)],
alignment: TextAlignment::Left,
linebreak_behaviour: BreakLineOn::WordBoundary,
},
text_2d_bounds: Text2dBounds {
// Wrap text in the rectangle
size: box_size,
Expand All @@ -87,6 +101,38 @@ 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 {
sections: vec![TextSection::new(
"this text wraps in the box\n(AnyCharacter linebreaks)",
slightly_smaller_text_style.clone(),
)],
alignment: TextAlignment::Left,
linebreak_behaviour: BreakLineOn::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