mirror of
https://github.com/bevyengine/bevy
synced 2025-01-24 19:05:17 +00:00
aeea4b0344
# 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`.
212 lines
7.2 KiB
Rust
212 lines
7.2 KiB
Rust
use bevy_asset::Handle;
|
|
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
|
|
use bevy_reflect::{prelude::*, FromReflect};
|
|
use bevy_render::color::Color;
|
|
use bevy_utils::default;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{Font, DEFAULT_FONT_HANDLE};
|
|
|
|
#[derive(Component, Debug, Clone, Reflect)]
|
|
#[reflect(Component, Default)]
|
|
pub struct Text {
|
|
pub sections: Vec<TextSection>,
|
|
/// 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_behavior: BreakLineOn,
|
|
}
|
|
|
|
impl Default for Text {
|
|
fn default() -> Self {
|
|
Self {
|
|
sections: Default::default(),
|
|
alignment: TextAlignment::Left,
|
|
linebreak_behavior: BreakLineOn::WordBoundary,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Text {
|
|
/// Constructs a [`Text`] with a single section.
|
|
///
|
|
/// ```
|
|
/// # use bevy_asset::Handle;
|
|
/// # use bevy_render::color::Color;
|
|
/// # use bevy_text::{Font, Text, TextStyle, TextAlignment};
|
|
/// #
|
|
/// # let font_handle: Handle<Font> = Default::default();
|
|
/// #
|
|
/// // Basic usage.
|
|
/// let hello_world = Text::from_section(
|
|
/// // Accepts a String or any type that converts into a String, such as &str.
|
|
/// "hello world!",
|
|
/// TextStyle {
|
|
/// font: font_handle.clone(),
|
|
/// font_size: 60.0,
|
|
/// color: Color::WHITE,
|
|
/// },
|
|
/// );
|
|
///
|
|
/// let hello_bevy = Text::from_section(
|
|
/// "hello bevy!",
|
|
/// TextStyle {
|
|
/// font: font_handle,
|
|
/// font_size: 60.0,
|
|
/// color: Color::WHITE,
|
|
/// },
|
|
/// ) // You can still add an alignment.
|
|
/// .with_alignment(TextAlignment::Center);
|
|
/// ```
|
|
pub fn from_section(value: impl Into<String>, style: TextStyle) -> Self {
|
|
Self {
|
|
sections: vec![TextSection::new(value, style)],
|
|
..default()
|
|
}
|
|
}
|
|
|
|
/// Constructs a [`Text`] from a list of sections.
|
|
///
|
|
/// ```
|
|
/// # use bevy_asset::Handle;
|
|
/// # use bevy_render::color::Color;
|
|
/// # use bevy_text::{Font, Text, TextStyle, TextSection};
|
|
/// #
|
|
/// # let font_handle: Handle<Font> = Default::default();
|
|
/// #
|
|
/// let hello_world = Text::from_sections([
|
|
/// TextSection::new(
|
|
/// "Hello, ",
|
|
/// TextStyle {
|
|
/// font: font_handle.clone(),
|
|
/// font_size: 60.0,
|
|
/// color: Color::BLUE,
|
|
/// },
|
|
/// ),
|
|
/// TextSection::new(
|
|
/// "World!",
|
|
/// TextStyle {
|
|
/// font: font_handle,
|
|
/// font_size: 60.0,
|
|
/// color: Color::RED,
|
|
/// },
|
|
/// ),
|
|
/// ]);
|
|
/// ```
|
|
pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
|
|
Self {
|
|
sections: sections.into_iter().collect(),
|
|
..default()
|
|
}
|
|
}
|
|
|
|
/// Returns this [`Text`] with a new [`TextAlignment`].
|
|
pub const fn with_alignment(mut self, alignment: TextAlignment) -> Self {
|
|
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)]
|
|
pub struct TextSection {
|
|
pub value: String,
|
|
pub style: TextStyle,
|
|
}
|
|
|
|
impl TextSection {
|
|
/// Create a new [`TextSection`].
|
|
pub fn new(value: impl Into<String>, style: TextStyle) -> Self {
|
|
Self {
|
|
value: value.into(),
|
|
style,
|
|
}
|
|
}
|
|
|
|
/// Create an empty [`TextSection`] from a style. Useful when the value will be set dynamically.
|
|
pub const fn from_style(style: TextStyle) -> Self {
|
|
Self {
|
|
value: String::new(),
|
|
style,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Describes horizontal alignment preference for positioning & bounds.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
|
|
#[reflect(Serialize, Deserialize)]
|
|
pub enum TextAlignment {
|
|
/// Leftmost character is immediately to the right of the render position.<br/>
|
|
/// Bounds start from the render position and advance rightwards.
|
|
Left,
|
|
/// Leftmost & rightmost characters are equidistant to the render position.<br/>
|
|
/// Bounds start from the render position and advance equally left & right.
|
|
Center,
|
|
/// Rightmost character is immediately to the left of the render position.<br/>
|
|
/// Bounds start from the render position and advance leftwards.
|
|
Right,
|
|
}
|
|
|
|
impl From<TextAlignment> for glyph_brush_layout::HorizontalAlign {
|
|
fn from(val: TextAlignment) -> Self {
|
|
match val {
|
|
TextAlignment::Left => glyph_brush_layout::HorizontalAlign::Left,
|
|
TextAlignment::Center => glyph_brush_layout::HorizontalAlign::Center,
|
|
TextAlignment::Right => glyph_brush_layout::HorizontalAlign::Right,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Reflect, FromReflect)]
|
|
pub struct TextStyle {
|
|
pub font: Handle<Font>,
|
|
pub font_size: f32,
|
|
pub color: Color,
|
|
}
|
|
|
|
impl Default for TextStyle {
|
|
fn default() -> Self {
|
|
Self {
|
|
font: DEFAULT_FONT_HANDLE.typed(),
|
|
font_size: 12.0,
|
|
color: Color::WHITE,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 behavior 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 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 {
|
|
// 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,
|
|
}
|
|
}
|
|
}
|