Add text wrapping support to Text2d (#4347)
# Objective Fixes #4344. ## Solution Add a new component `Text2dBounds` to `Text2dBundle` that specifies the maximum width and height of text. Text will wrap according to this size.
This commit is contained in:
parent
c7c08f95cb
commit
954022c799
@ -1,6 +1,5 @@
|
|||||||
use bevy_asset::Handle;
|
use bevy_asset::Handle;
|
||||||
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
|
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
|
||||||
use bevy_math::Size;
|
|
||||||
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize};
|
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize};
|
||||||
use bevy_render::color::Color;
|
use bevy_render::color::Color;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -150,9 +149,3 @@ impl Default for TextStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct Text2dSize {
|
|
||||||
pub size: Size,
|
|
||||||
}
|
|
||||||
|
@ -1,46 +1,63 @@
|
|||||||
use bevy_asset::Assets;
|
use bevy_asset::Assets;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
query::{Changed, QueryState, With},
|
query::{Changed, QueryState, With},
|
||||||
|
reflect::ReflectComponent,
|
||||||
system::{Local, Query, QuerySet, Res, ResMut},
|
system::{Local, Query, QuerySet, Res, ResMut},
|
||||||
};
|
};
|
||||||
use bevy_math::{Size, Vec3};
|
use bevy_math::{Size, Vec3};
|
||||||
|
use bevy_reflect::Reflect;
|
||||||
use bevy_render::{texture::Image, view::Visibility, RenderWorld};
|
use bevy_render::{texture::Image, view::Visibility, RenderWorld};
|
||||||
use bevy_sprite::{ExtractedSprite, ExtractedSprites, TextureAtlas};
|
use bevy_sprite::{ExtractedSprite, ExtractedSprites, TextureAtlas};
|
||||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||||
use bevy_window::{WindowId, Windows};
|
use bevy_window::{WindowId, Windows};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, Text2dSize, TextError,
|
DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, VerticalAlign,
|
||||||
VerticalAlign,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The calculated size of text drawn in 2D scene.
|
||||||
|
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct Text2dSize {
|
||||||
|
pub size: Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The maximum width and height of text. The text will wrap according to the specified size.
|
||||||
|
/// Characters out of the bounds after wrapping will be truncated. Text is aligned according to the
|
||||||
|
/// specified `TextAlignment`.
|
||||||
|
///
|
||||||
|
/// Note: only characters that are completely out of the bounds will be truncated, so this is not a
|
||||||
|
/// reliable limit if it is necessary to contain the text strictly in the bounds. Currently this
|
||||||
|
/// component is mainly useful for text wrapping only.
|
||||||
|
#[derive(Component, Copy, Clone, Debug, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct Text2dBounds {
|
||||||
|
pub size: Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Text2dBounds {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
size: Size::new(f32::MAX, f32::MAX),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The bundle of components needed to draw text in a 2D scene via a 2D `OrthographicCameraBundle`.
|
/// The bundle of components needed to draw text in a 2D scene via a 2D `OrthographicCameraBundle`.
|
||||||
/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/text2d.rs)
|
/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/text2d.rs)
|
||||||
#[derive(Bundle, Clone, Debug)]
|
#[derive(Bundle, Clone, Debug, Default)]
|
||||||
pub struct Text2dBundle {
|
pub struct Text2dBundle {
|
||||||
pub text: Text,
|
pub text: Text,
|
||||||
pub transform: Transform,
|
pub transform: Transform,
|
||||||
pub global_transform: GlobalTransform,
|
pub global_transform: GlobalTransform,
|
||||||
pub text_2d_size: Text2dSize,
|
pub text_2d_size: Text2dSize,
|
||||||
|
pub text_2d_bounds: Text2dBounds,
|
||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Text2dBundle {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
text: Default::default(),
|
|
||||||
transform: Default::default(),
|
|
||||||
global_transform: Default::default(),
|
|
||||||
text_2d_size: Text2dSize {
|
|
||||||
size: Size::default(),
|
|
||||||
},
|
|
||||||
visibility: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_text2d_sprite(
|
pub fn extract_text2d_sprite(
|
||||||
mut render_world: ResMut<RenderWorld>,
|
mut render_world: ResMut<RenderWorld>,
|
||||||
texture_atlases: Res<Assets<TextureAtlas>>,
|
texture_atlases: Res<Assets<TextureAtlas>>,
|
||||||
@ -123,7 +140,7 @@ pub fn text2d_system(
|
|||||||
mut text_pipeline: ResMut<DefaultTextPipeline>,
|
mut text_pipeline: ResMut<DefaultTextPipeline>,
|
||||||
mut text_queries: QuerySet<(
|
mut text_queries: QuerySet<(
|
||||||
QueryState<Entity, (With<Text2dSize>, Changed<Text>)>,
|
QueryState<Entity, (With<Text2dSize>, Changed<Text>)>,
|
||||||
QueryState<(&Text, &mut Text2dSize), With<Text2dSize>>,
|
QueryState<(&Text, Option<&Text2dBounds>, &mut Text2dSize), With<Text2dSize>>,
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
// Adds all entities where the text or the style has changed to the local queue
|
// Adds all entities where the text or the style has changed to the local queue
|
||||||
@ -141,14 +158,21 @@ pub fn text2d_system(
|
|||||||
let mut new_queue = Vec::new();
|
let mut new_queue = Vec::new();
|
||||||
let mut query = text_queries.q1();
|
let mut query = text_queries.q1();
|
||||||
for entity in queued_text.entities.drain(..) {
|
for entity in queued_text.entities.drain(..) {
|
||||||
if let Ok((text, mut calculated_size)) = query.get_mut(entity) {
|
if let Ok((text, bounds, mut calculated_size)) = query.get_mut(entity) {
|
||||||
|
let text_bounds = match bounds {
|
||||||
|
Some(bounds) => Size {
|
||||||
|
width: scale_value(bounds.size.width, scale_factor),
|
||||||
|
height: scale_value(bounds.size.height, scale_factor),
|
||||||
|
},
|
||||||
|
None => Size::new(f32::MAX, f32::MAX),
|
||||||
|
};
|
||||||
match text_pipeline.queue_text(
|
match text_pipeline.queue_text(
|
||||||
entity,
|
entity,
|
||||||
&fonts,
|
&fonts,
|
||||||
&text.sections,
|
&text.sections,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
text.alignment,
|
text.alignment,
|
||||||
Size::new(f32::MAX, f32::MAX),
|
text_bounds,
|
||||||
&mut *font_atlas_set_storage,
|
&mut *font_atlas_set_storage,
|
||||||
&mut *texture_atlases,
|
&mut *texture_atlases,
|
||||||
&mut *textures,
|
&mut *textures,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::{prelude::*, text::Text2dBounds};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
@ -47,10 +47,46 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
// Demonstrate changing scale
|
// Demonstrate changing scale
|
||||||
commands
|
commands
|
||||||
.spawn_bundle(Text2dBundle {
|
.spawn_bundle(Text2dBundle {
|
||||||
text: Text::with_section("scale", text_style, text_alignment),
|
text: Text::with_section("scale", text_style.clone(), text_alignment),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(AnimateScale);
|
.insert(AnimateScale);
|
||||||
|
// Demonstrate text wrapping
|
||||||
|
let box_size = Size::new(300.0, 200.0);
|
||||||
|
let box_position = Vec2::new(0.0, -250.0);
|
||||||
|
commands.spawn_bundle(SpriteBundle {
|
||||||
|
sprite: Sprite {
|
||||||
|
color: Color::rgb(0.25, 0.25, 0.75),
|
||||||
|
custom_size: Some(Vec2::new(box_size.width, box_size.height)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_translation(box_position.extend(0.0)),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
let text_alignment_topleft = TextAlignment {
|
||||||
|
vertical: VerticalAlign::Top,
|
||||||
|
horizontal: HorizontalAlign::Left,
|
||||||
|
};
|
||||||
|
commands.spawn_bundle(Text2dBundle {
|
||||||
|
text: Text::with_section(
|
||||||
|
"this text wraps in the box",
|
||||||
|
text_style,
|
||||||
|
text_alignment_topleft,
|
||||||
|
),
|
||||||
|
text_2d_bounds: Text2dBounds {
|
||||||
|
// Wrap text in the rectangle
|
||||||
|
size: box_size,
|
||||||
|
},
|
||||||
|
// We align text to the top-left, so this transform is the top-left corner of our text. The
|
||||||
|
// box is centered at box_position, so it is necessary to move by half of the box size to
|
||||||
|
// keep the text in the box.
|
||||||
|
transform: Transform::from_xyz(
|
||||||
|
box_position.x - box_size.width / 2.0,
|
||||||
|
box_position.y + box_size.height / 2.0,
|
||||||
|
1.0,
|
||||||
|
),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn animate_translation(
|
fn animate_translation(
|
||||||
|
Loading…
Reference in New Issue
Block a user