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] - Add text wrapping support to Text2d #4347

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use bevy_asset::Handle;
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
use bevy_math::Size;
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize};
use bevy_render::color::Color;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -150,9 +149,3 @@ impl Default for TextStyle {
}
}
}

#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct Text2dSize {
pub size: Size,
}
63 changes: 43 additions & 20 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,62 @@
use bevy_asset::Assets;
use bevy_ecs::{
bundle::Bundle,
component::Component,
entity::Entity,
query::{Changed, QueryState, With},
reflect::ReflectComponent,
system::{Local, Query, QuerySet, Res, ResMut},
};
use bevy_math::{Size, Vec3};
use bevy_reflect::Reflect;
use bevy_render::{texture::Image, view::Visibility, RenderWorld};
use bevy_sprite::{ExtractedSprite, ExtractedSprites, TextureAtlas};
use bevy_transform::prelude::{GlobalTransform, Transform};
use bevy_window::{WindowId, Windows};

use crate::{
DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, Text2dSize, TextError,
VerticalAlign,
DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment on what happens when it's not possible to contain the text within the specified bound?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my testing and code-reading, the underlying library will truncate the part of text that fall out of the bound. However, only characters that are completely out of the bound will be truncated, so there may be some characters partly out of the bound.

This is not very desirable. A better behavior would be to clip the character at the bound, but as I understand, this is not simple because we don't have sprite masking yet. Therefore, I decided to simply document this fact here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a comment for now could be enough. Could you also mention that the text is centered within the bounds and when wrapped, and not justified or left align

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This centering is a simple consequence of the text alignment choice here (center horizontally and vertically). I added a mention of TextAlignment here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh right, I missed that it was part of the Text component. Thanks!

/// Characters out of the bounds after wrapping will be truncated.
///
/// 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`.
/// [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 text: Text,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub text_2d_size: Text2dSize,
pub text_2d_bounds: Text2dBounds,
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(
mut render_world: ResMut<RenderWorld>,
texture_atlases: Res<Assets<TextureAtlas>>,
Expand Down Expand Up @@ -123,7 +139,7 @@ pub fn text2d_system(
mut text_pipeline: ResMut<DefaultTextPipeline>,
mut text_queries: QuerySet<(
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
Expand All @@ -141,14 +157,21 @@ pub fn text2d_system(
let mut new_queue = Vec::new();
let mut query = text_queries.q1();
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(
entity,
&fonts,
&text.sections,
scale_factor,
text.alignment,
Size::new(f32::MAX, f32::MAX),
text_bounds,
&mut *font_atlas_set_storage,
&mut *texture_atlases,
&mut *textures,
Expand Down
23 changes: 21 additions & 2 deletions examples/2d/text2d.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bevy::prelude::*;
use bevy::{prelude::*, text::Text2dBounds};

fn main() {
App::new()
Expand Down Expand Up @@ -47,10 +47,29 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Demonstrate changing scale
commands
.spawn_bundle(Text2dBundle {
text: Text::with_section("scale", text_style, text_alignment),
text: Text::with_section("scale", text_style.clone(), text_alignment),
..default()
})
.insert(AnimateScale);
// Demonstrate text wrapping
commands.spawn_bundle(SpriteBundle {
sprite: Sprite {
color: Color::rgb(0.25, 0.25, 0.75),
custom_size: Some(Vec2::new(300.0, 200.0)),
..default()
},
transform: Transform::from_xyz(0.0, -250.0, 0.0),
..default()
});
commands.spawn_bundle(Text2dBundle {
text: Text::with_section("this text wraps in the box", text_style, text_alignment),
text_2d_bounds: Text2dBounds {
// Wrap text in the rectangle
size: Size::new(300.0, 200.0),
},
transform: Transform::from_xyz(0.0, -250.0, 1.0),
..default()
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use variables for the bounds so that they are reused for the sprite bundle and the text bounds? That would make it easier for someone who wants to play with the example

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I also changed the text alignment to top-left here, as this is probably a more useful case.

}

fn animate_translation(
Expand Down