mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Allow users of Text/TextBundle to choose from glyph_brush_layout's BuiltInLineBreaker options. (#7283)
# Objective Currently, Text always uses the default linebreaking behaviour in glyph_brush_layout `BuiltInLineBreaker::Unicode` which breaks lines at word boundaries. However, glyph_brush_layout also supports breaking lines at any character by setting the linebreaker to `BuiltInLineBreaker::AnyChar`. Having text wrap character-by-character instead of at word boundaries is desirable in some cases - consider that consoles/terminals usually wrap this way. As a side note, the default Unicode linebreaker does not seem to handle emergency cases, where there is no word boundary on a line to break at. In that case, the text runs out of bounds. Issue #1867 shows an example of this. ## Solution Basically just copies how TextAlignment is exposed, but for a new enum TextLineBreakBehaviour. This PR exposes glyph_brush_layout's two simple linebreaking options (Unicode, AnyChar) to users of Text via the enum TextLineBreakBehaviour (which just translates those 2 aforementioned options), plus a method 'with_linebreak_behaviour' on Text and TextBundle. ## Changelog Added `Text::with_linebreak_behaviour` Added `TextBundle::with_linebreak_behaviour` `TextPipeline::queue_text` and `GlyphBrush::compute_glyphs` now need a TextLineBreakBehaviour argument, in order to pass through the new field. Modified the `text2d` example to show both linebreaking behaviours. ## Example Here's what the modified example looks like ![image](https://user-images.githubusercontent.com/117271367/213589184-b1a54bf3-116c-4721-8cb6-1cb69edb3070.png)
This commit is contained in:
parent
a94830f0c9
commit
cef56a0d47
6 changed files with 94 additions and 13 deletions
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
@ -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>,
|
||||
|
@ -75,9 +76,9 @@ impl TextPipeline {
|
|||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let section_glyphs = self
|
||||
.brush
|
||||
.compute_glyphs(§ions, bounds, text_alignment)?;
|
||||
let section_glyphs =
|
||||
self.brush
|
||||
.compute_glyphs(§ions, bounds, text_alignment, linebreak_behaviour)?;
|
||||
|
||||
if section_glyphs.is_empty() {
|
||||
return Ok(TextLayoutInfo::default());
|
||||
|
|
|
@ -14,6 +14,8 @@ 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 {
|
||||
|
@ -21,6 +23,7 @@ impl Default for Text {
|
|||
Self {
|
||||
sections: Default::default(),
|
||||
alignment: TextAlignment::Left,
|
||||
linebreak_behaviour: BreakLineOn::WordBoundary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue