 ead84e0e3d
			
		
	
	
		ead84e0e3d
		
			
		
	
	
	
	
		
			
			# Objective - Improve code quality in preparation for https://github.com/bevyengine/bevy/discussions/15014 ## Solution - Rename BreakLineOn to LineBreak. ## Migration Guide `BreakLineOn` was renamed to `LineBreak`, and paramters named `linebreak_behavior` were renamed to `linebreak`.
		
			
				
	
	
		
			296 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use bevy_asset::Handle;
 | ||
| use bevy_color::Color;
 | ||
| use bevy_derive::{Deref, DerefMut};
 | ||
| use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
 | ||
| use bevy_reflect::prelude::*;
 | ||
| use bevy_utils::default;
 | ||
| use cosmic_text::{Buffer, Metrics};
 | ||
| use serde::{Deserialize, Serialize};
 | ||
| 
 | ||
| use crate::Font;
 | ||
| pub use cosmic_text::{
 | ||
|     self, FamilyOwned as FontFamily, Stretch as FontStretch, Style as FontStyle,
 | ||
|     Weight as FontWeight,
 | ||
| };
 | ||
| 
 | ||
| /// Wrapper for [`cosmic_text::Buffer`]
 | ||
| #[derive(Component, Deref, DerefMut, Debug, Clone)]
 | ||
| pub struct CosmicBuffer(pub Buffer);
 | ||
| 
 | ||
| impl Default for CosmicBuffer {
 | ||
|     fn default() -> Self {
 | ||
|         Self(Buffer::new_empty(Metrics::new(0.0, 0.000001)))
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /// A component that is the entry point for rendering text.
 | ||
| ///
 | ||
| /// It contains all of the text value and styling information.
 | ||
| #[derive(Component, Debug, Clone, Default, Reflect)]
 | ||
| #[reflect(Component, Default, Debug)]
 | ||
| pub struct Text {
 | ||
|     /// The text's sections
 | ||
|     pub sections: Vec<TextSection>,
 | ||
|     /// The text's internal alignment.
 | ||
|     /// Should not affect its position within a container.
 | ||
|     pub justify: JustifyText,
 | ||
|     /// How the text should linebreak when running out of the bounds determined by `max_size`
 | ||
|     pub linebreak: LineBreak,
 | ||
|     /// The antialiasing method to use when rendering text.
 | ||
|     pub font_smoothing: FontSmoothing,
 | ||
| }
 | ||
| 
 | ||
| impl Text {
 | ||
|     /// Constructs a [`Text`] with a single section.
 | ||
|     ///
 | ||
|     /// ```
 | ||
|     /// # use bevy_asset::Handle;
 | ||
|     /// # use bevy_color::Color;
 | ||
|     /// # use bevy_text::{Font, Text, TextStyle, JustifyText};
 | ||
|     /// #
 | ||
|     /// # 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().into(),
 | ||
|     ///         font_size: 60.0,
 | ||
|     ///         color: Color::WHITE,
 | ||
|     ///     },
 | ||
|     /// );
 | ||
|     ///
 | ||
|     /// let hello_bevy = Text::from_section(
 | ||
|     ///     "hello world\nand bevy!",
 | ||
|     ///     TextStyle {
 | ||
|     ///         font: font_handle.into(),
 | ||
|     ///         font_size: 60.0,
 | ||
|     ///         color: Color::WHITE,
 | ||
|     ///     },
 | ||
|     /// ) // You can still add text justifaction.
 | ||
|     /// .with_justify(JustifyText::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_color::Color;
 | ||
|     /// # use bevy_color::palettes::basic::{RED, BLUE};
 | ||
|     /// # 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().into(),
 | ||
|     ///             font_size: 60.0,
 | ||
|     ///             color: BLUE.into(),
 | ||
|     ///         },
 | ||
|     ///     ),
 | ||
|     ///     TextSection::new(
 | ||
|     ///         "World!",
 | ||
|     ///         TextStyle {
 | ||
|     ///             font: font_handle.into(),
 | ||
|     ///             font_size: 60.0,
 | ||
|     ///             color: RED.into(),
 | ||
|     ///         },
 | ||
|     ///     ),
 | ||
|     /// ]);
 | ||
|     /// ```
 | ||
|     pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
 | ||
|         Self {
 | ||
|             sections: sections.into_iter().collect(),
 | ||
|             ..default()
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Returns this [`Text`] with a new [`JustifyText`].
 | ||
|     pub const fn with_justify(mut self, justify: JustifyText) -> Self {
 | ||
|         self.justify = justify;
 | ||
|         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 = LineBreak::NoWrap;
 | ||
|         self
 | ||
|     }
 | ||
| 
 | ||
|     /// Returns this [`Text`] with the specified [`FontSmoothing`].
 | ||
|     pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self {
 | ||
|         self.font_smoothing = font_smoothing;
 | ||
|         self
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /// Contains the value of the text in a section and how it should be styled.
 | ||
| #[derive(Debug, Default, Clone, Reflect)]
 | ||
| #[reflect(Default)]
 | ||
| pub struct TextSection {
 | ||
|     /// The content (in `String` form) of the text in the section.
 | ||
|     pub value: String,
 | ||
|     /// The style of the text in the section, including the font face, font size, and color.
 | ||
|     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,
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl From<&str> for TextSection {
 | ||
|     fn from(value: &str) -> Self {
 | ||
|         Self {
 | ||
|             value: value.into(),
 | ||
|             ..default()
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl From<String> for TextSection {
 | ||
|     fn from(value: String) -> Self {
 | ||
|         Self {
 | ||
|             value,
 | ||
|             ..Default::default()
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /// Describes the horizontal alignment of multiple lines of text relative to each other.
 | ||
| ///
 | ||
| /// This only affects the internal positioning of the lines of text within a text entity and
 | ||
| /// does not affect the text entity's position.
 | ||
| ///
 | ||
| /// _Has no affect on a single line text entity._
 | ||
| #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
 | ||
| #[reflect(Serialize, Deserialize)]
 | ||
| pub enum JustifyText {
 | ||
|     /// Leftmost character is immediately to the right of the render position.
 | ||
|     /// Bounds start from the render position and advance rightwards.
 | ||
|     #[default]
 | ||
|     Left,
 | ||
|     /// Leftmost & rightmost characters are equidistant to the render position.
 | ||
|     /// Bounds start from the render position and advance equally left & right.
 | ||
|     Center,
 | ||
|     /// Rightmost character is immediately to the left of the render position.
 | ||
|     /// Bounds start from the render position and advance leftwards.
 | ||
|     Right,
 | ||
|     /// Words are spaced so that leftmost & rightmost characters
 | ||
|     /// align with their margins.
 | ||
|     /// Bounds start from the render position and advance equally left & right.
 | ||
|     Justified,
 | ||
| }
 | ||
| 
 | ||
| impl From<JustifyText> for cosmic_text::Align {
 | ||
|     fn from(justify: JustifyText) -> Self {
 | ||
|         match justify {
 | ||
|             JustifyText::Left => cosmic_text::Align::Left,
 | ||
|             JustifyText::Center => cosmic_text::Align::Center,
 | ||
|             JustifyText::Right => cosmic_text::Align::Right,
 | ||
|             JustifyText::Justified => cosmic_text::Align::Justified,
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Clone, Debug, Reflect)]
 | ||
| /// `TextStyle` determines the style of the text in a section, specifically
 | ||
| /// the font face, the font size, and the color.
 | ||
| pub struct TextStyle {
 | ||
|     /// The specific font face to use, as a `Handle` to a [`Font`] asset.
 | ||
|     ///
 | ||
|     /// If the `font` is not specified, then
 | ||
|     /// * if `default_font` feature is enabled (enabled by default in `bevy` crate),
 | ||
|     ///   `FiraMono-subset.ttf` compiled into the library is used.
 | ||
|     /// * otherwise no text will be rendered, unless a custom font is loaded into the default font
 | ||
|     ///   handle.
 | ||
|     pub font: Handle<Font>,
 | ||
|     /// The vertical height of rasterized glyphs in the font atlas in pixels.
 | ||
|     ///
 | ||
|     /// This is multiplied by the window scale factor and `UiScale`, but not the text entity
 | ||
|     /// transform or camera projection.
 | ||
|     ///
 | ||
|     /// A new font atlas is generated for every combination of font handle and scaled font size
 | ||
|     /// which can have a strong performance impact.
 | ||
|     pub font_size: f32,
 | ||
|     /// The color of the text for this section.
 | ||
|     pub color: Color,
 | ||
| }
 | ||
| 
 | ||
| impl Default for TextStyle {
 | ||
|     fn default() -> Self {
 | ||
|         Self {
 | ||
|             font: Default::default(),
 | ||
|             font_size: 20.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, Default, Reflect, Serialize, Deserialize)]
 | ||
| #[reflect(Serialize, Deserialize)]
 | ||
| pub enum LineBreak {
 | ||
|     /// 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.
 | ||
|     #[default]
 | ||
|     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,
 | ||
|     /// Wraps at the word level, or fallback to character level if a word can’t fit on a line by itself
 | ||
|     WordOrCharacter,
 | ||
|     /// 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,
 | ||
| }
 | ||
| 
 | ||
| /// Determines which antialiasing method to use when rendering text. By default, text is
 | ||
| /// rendered with grayscale antialiasing, but this can be changed to achieve a pixelated look.
 | ||
| ///
 | ||
| /// **Note:** Subpixel antialiasing is not currently supported.
 | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)]
 | ||
| #[reflect(Serialize, Deserialize)]
 | ||
| #[doc(alias = "antialiasing")]
 | ||
| #[doc(alias = "pixelated")]
 | ||
| pub enum FontSmoothing {
 | ||
|     /// No antialiasing. Useful for when you want to render text with a pixel art aesthetic.
 | ||
|     ///
 | ||
|     /// Combine this with `UiAntiAlias::Off` and `Msaa::Off` on your 2D camera for a fully pixelated look.
 | ||
|     ///
 | ||
|     /// **Note:** Due to limitations of the underlying text rendering library,
 | ||
|     /// this may require specially-crafted pixel fonts to look good, especially at small sizes.
 | ||
|     None,
 | ||
|     /// The default grayscale antialiasing. Produces text that looks smooth,
 | ||
|     /// even at small font sizes and low resolutions with modern vector fonts.
 | ||
|     #[default]
 | ||
|     AntiAliased,
 | ||
|     // TODO: Add subpixel antialias support
 | ||
|     // SubpixelAntiAliased,
 | ||
| }
 |