Skip to content

Commit 9eefd7c

Browse files
committed
Remove VerticalAlign from TextAlignment (#6807)
# Objective Remove the `VerticalAlign` enum. Text's alignment field should only affect the text's internal text alignment, not its position. The only way to control a `TextBundle`'s position and bounds should be through the manipulation of the constraints in the `Style` components of the nodes in the Bevy UI's layout tree. `Text2dBundle` should have a separate `Anchor` component that sets its position relative to its transform. Related issues: #676, #1490, #5502, #5513, #5834, #6717, #6724, #6741, #6748 ## Changelog * Changed `TextAlignment` into an enum with `Left`, `Center`, and `Right` variants. * Removed the `HorizontalAlign` and `VerticalAlign` types. * Added an `Anchor` component to `Text2dBundle` * Added `Component` derive to `Anchor` * Use `f32::INFINITY` instead of `f32::MAX` to represent unbounded text in Text2dBounds ## Migration Guide The `alignment` field of `Text` now only affects the text's internal alignment. ### Change `TextAlignment` to TextAlignment` which is now an enum. Replace: * `TextAlignment::TOP_LEFT`, `TextAlignment::CENTER_LEFT`, `TextAlignment::BOTTOM_LEFT` with `TextAlignment::Left` * `TextAlignment::TOP_CENTER`, `TextAlignment::CENTER_LEFT`, `TextAlignment::BOTTOM_CENTER` with `TextAlignment::Center` * `TextAlignment::TOP_RIGHT`, `TextAlignment::CENTER_RIGHT`, `TextAlignment::BOTTOM_RIGHT` with `TextAlignment::Right` ### Changes for `Text2dBundle` `Text2dBundle` has a new field 'text_anchor' that takes an `Anchor` component that controls its position relative to its transform.
1 parent 4ff50f6 commit 9eefd7c

File tree

11 files changed

+88
-196
lines changed

11 files changed

+88
-196
lines changed

crates/bevy_sprite/src/sprite.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub struct Sprite {
2424

2525
/// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform).
2626
/// It defaults to `Anchor::Center`.
27-
#[derive(Debug, Clone, Default, Reflect)]
27+
#[derive(Component, Debug, Clone, Default, Reflect)]
2828
#[doc(alias = "pivot")]
2929
pub enum Anchor {
3030
#[default]

crates/bevy_text/src/glyph_brush.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ impl GlyphBrush {
4141
..Default::default()
4242
};
4343
let section_glyphs = Layout::default()
44-
.h_align(text_alignment.horizontal.into())
45-
.v_align(text_alignment.vertical.into())
44+
.h_align(text_alignment.into())
4645
.calculate_glyphs(&self.fonts, &geom, sections);
4746
Ok(section_glyphs)
4847
}

crates/bevy_text/src/lib.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ pub use text2d::*;
2020

2121
pub mod prelude {
2222
#[doc(hidden)]
23-
pub use crate::{
24-
Font, HorizontalAlign, Text, Text2dBundle, TextAlignment, TextError, TextSection,
25-
TextStyle, VerticalAlign,
26-
};
23+
pub use crate::{Font, Text, Text2dBundle, TextAlignment, TextError, TextSection, TextStyle};
2724
}
2825

2926
use bevy_app::prelude::*;
@@ -77,9 +74,8 @@ impl Plugin for TextPlugin {
7774
.register_type::<TextSection>()
7875
.register_type::<Vec<TextSection>>()
7976
.register_type::<TextStyle>()
77+
.register_type::<Text>()
8078
.register_type::<TextAlignment>()
81-
.register_type::<VerticalAlign>()
82-
.register_type::<HorizontalAlign>()
8379
.init_asset_loader::<FontLoader>()
8480
.init_resource::<TextSettings>()
8581
.init_resource::<FontAtlasWarning>()

crates/bevy_text/src/text.rs

Lines changed: 23 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,36 @@ use bevy_asset::Handle;
22
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
33
use bevy_reflect::{prelude::*, FromReflect};
44
use bevy_render::color::Color;
5+
use bevy_utils::default;
56
use serde::{Deserialize, Serialize};
67

78
use crate::Font;
89

9-
#[derive(Component, Debug, Default, Clone, Reflect)]
10+
#[derive(Component, Debug, Clone, Reflect)]
1011
#[reflect(Component, Default)]
1112
pub struct Text {
1213
pub sections: Vec<TextSection>,
14+
/// The text's internal alignment.
15+
/// Should not affect its position within a container.
1316
pub alignment: TextAlignment,
1417
}
1518

19+
impl Default for Text {
20+
fn default() -> Self {
21+
Self {
22+
sections: Default::default(),
23+
alignment: TextAlignment::Left,
24+
}
25+
}
26+
}
27+
1628
impl Text {
1729
/// Constructs a [`Text`] with a single section.
1830
///
1931
/// ```
2032
/// # use bevy_asset::Handle;
2133
/// # use bevy_render::color::Color;
22-
/// # use bevy_text::{Font, Text, TextAlignment, TextStyle, HorizontalAlign, VerticalAlign};
34+
/// # use bevy_text::{Font, Text, TextStyle, TextAlignment};
2335
/// #
2436
/// # let font_handle: Handle<Font> = Default::default();
2537
/// #
@@ -42,12 +54,12 @@ impl Text {
4254
/// color: Color::WHITE,
4355
/// },
4456
/// ) // You can still add an alignment.
45-
/// .with_alignment(TextAlignment::CENTER);
57+
/// .with_alignment(TextAlignment::Center);
4658
/// ```
4759
pub fn from_section(value: impl Into<String>, style: TextStyle) -> Self {
4860
Self {
4961
sections: vec![TextSection::new(value, style)],
50-
alignment: Default::default(),
62+
..default()
5163
}
5264
}
5365

@@ -82,7 +94,7 @@ impl Text {
8294
pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
8395
Self {
8496
sections: sections.into_iter().collect(),
85-
alignment: Default::default(),
97+
..default()
8698
}
8799
}
88100

@@ -117,78 +129,10 @@ impl TextSection {
117129
}
118130
}
119131

120-
#[derive(Debug, Clone, Copy, Reflect)]
121-
pub struct TextAlignment {
122-
pub vertical: VerticalAlign,
123-
pub horizontal: HorizontalAlign,
124-
}
125-
126-
impl TextAlignment {
127-
/// A [`TextAlignment`] set to the top-left.
128-
pub const TOP_LEFT: Self = TextAlignment {
129-
vertical: VerticalAlign::Top,
130-
horizontal: HorizontalAlign::Left,
131-
};
132-
133-
/// A [`TextAlignment`] set to the top-center.
134-
pub const TOP_CENTER: Self = TextAlignment {
135-
vertical: VerticalAlign::Top,
136-
horizontal: HorizontalAlign::Center,
137-
};
138-
139-
/// A [`TextAlignment`] set to the top-right.
140-
pub const TOP_RIGHT: Self = TextAlignment {
141-
vertical: VerticalAlign::Top,
142-
horizontal: HorizontalAlign::Right,
143-
};
144-
145-
/// A [`TextAlignment`] set to center the center-left.
146-
pub const CENTER_LEFT: Self = TextAlignment {
147-
vertical: VerticalAlign::Center,
148-
horizontal: HorizontalAlign::Left,
149-
};
150-
151-
/// A [`TextAlignment`] set to center on both axes.
152-
pub const CENTER: Self = TextAlignment {
153-
vertical: VerticalAlign::Center,
154-
horizontal: HorizontalAlign::Center,
155-
};
156-
157-
/// A [`TextAlignment`] set to the center-right.
158-
pub const CENTER_RIGHT: Self = TextAlignment {
159-
vertical: VerticalAlign::Center,
160-
horizontal: HorizontalAlign::Right,
161-
};
162-
163-
/// A [`TextAlignment`] set to the bottom-left.
164-
pub const BOTTOM_LEFT: Self = TextAlignment {
165-
vertical: VerticalAlign::Bottom,
166-
horizontal: HorizontalAlign::Left,
167-
};
168-
169-
/// A [`TextAlignment`] set to the bottom-center.
170-
pub const BOTTOM_CENTER: Self = TextAlignment {
171-
vertical: VerticalAlign::Bottom,
172-
horizontal: HorizontalAlign::Center,
173-
};
174-
175-
/// A [`TextAlignment`] set to the bottom-right.
176-
pub const BOTTOM_RIGHT: Self = TextAlignment {
177-
vertical: VerticalAlign::Bottom,
178-
horizontal: HorizontalAlign::Right,
179-
};
180-
}
181-
182-
impl Default for TextAlignment {
183-
fn default() -> Self {
184-
TextAlignment::TOP_LEFT
185-
}
186-
}
187-
188132
/// Describes horizontal alignment preference for positioning & bounds.
189133
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
190134
#[reflect(Serialize, Deserialize)]
191-
pub enum HorizontalAlign {
135+
pub enum TextAlignment {
192136
/// Leftmost character is immediately to the right of the render position.<br/>
193137
/// Bounds start from the render position and advance rightwards.
194138
Left,
@@ -200,35 +144,12 @@ pub enum HorizontalAlign {
200144
Right,
201145
}
202146

203-
impl From<HorizontalAlign> for glyph_brush_layout::HorizontalAlign {
204-
fn from(val: HorizontalAlign) -> Self {
205-
match val {
206-
HorizontalAlign::Left => glyph_brush_layout::HorizontalAlign::Left,
207-
HorizontalAlign::Center => glyph_brush_layout::HorizontalAlign::Center,
208-
HorizontalAlign::Right => glyph_brush_layout::HorizontalAlign::Right,
209-
}
210-
}
211-
}
212-
213-
/// Describes vertical alignment preference for positioning & bounds. Currently a placeholder
214-
/// for future functionality.
215-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
216-
#[reflect(Serialize, Deserialize)]
217-
pub enum VerticalAlign {
218-
/// Characters/bounds start underneath the render position and progress downwards.
219-
Top,
220-
/// Characters/bounds center at the render position and progress outward equally.
221-
Center,
222-
/// Characters/bounds start above the render position and progress upward.
223-
Bottom,
224-
}
225-
226-
impl From<VerticalAlign> for glyph_brush_layout::VerticalAlign {
227-
fn from(val: VerticalAlign) -> Self {
147+
impl From<TextAlignment> for glyph_brush_layout::HorizontalAlign {
148+
fn from(val: TextAlignment) -> Self {
228149
match val {
229-
VerticalAlign::Top => glyph_brush_layout::VerticalAlign::Top,
230-
VerticalAlign::Center => glyph_brush_layout::VerticalAlign::Center,
231-
VerticalAlign::Bottom => glyph_brush_layout::VerticalAlign::Bottom,
150+
TextAlignment::Left => glyph_brush_layout::HorizontalAlign::Left,
151+
TextAlignment::Center => glyph_brush_layout::HorizontalAlign::Center,
152+
TextAlignment::Right => glyph_brush_layout::HorizontalAlign::Right,
232153
}
233154
}
234155
}

crates/bevy_text/src/text2d.rs

Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,10 @@ use bevy_utils::HashSet;
2222
use bevy_window::{WindowId, WindowScaleFactorChanged, Windows};
2323

2424
use crate::{
25-
Font, FontAtlasSet, FontAtlasWarning, HorizontalAlign, Text, TextError, TextLayoutInfo,
26-
TextPipeline, TextSettings, VerticalAlign, YAxisOrientation,
25+
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
26+
TextSettings, YAxisOrientation,
2727
};
2828

29-
/// The calculated size of text drawn in 2D scene.
30-
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
31-
#[reflect(Component)]
32-
pub struct Text2dSize {
33-
pub size: Vec2,
34-
}
35-
3629
/// The maximum width and height of text. The text will wrap according to the specified size.
3730
/// Characters out of the bounds after wrapping will be truncated. Text is aligned according to the
3831
/// specified `TextAlignment`.
@@ -47,21 +40,27 @@ pub struct Text2dBounds {
4740
}
4841

4942
impl Default for Text2dBounds {
43+
#[inline]
5044
fn default() -> Self {
51-
Self {
52-
size: Vec2::new(f32::MAX, f32::MAX),
53-
}
45+
Self::UNBOUNDED
5446
}
5547
}
5648

49+
impl Text2dBounds {
50+
/// Unbounded text will not be truncated or wrapped.
51+
pub const UNBOUNDED: Self = Self {
52+
size: Vec2::splat(f32::INFINITY),
53+
};
54+
}
55+
5756
/// The bundle of components needed to draw text in a 2D scene via a 2D `Camera2dBundle`.
5857
/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/text2d.rs)
5958
#[derive(Bundle, Clone, Debug, Default)]
6059
pub struct Text2dBundle {
6160
pub text: Text,
61+
pub text_anchor: Anchor,
6262
pub transform: Transform,
6363
pub global_transform: GlobalTransform,
64-
pub text_2d_size: Text2dSize,
6564
pub text_2d_bounds: Text2dBounds,
6665
pub visibility: Visibility,
6766
pub computed_visibility: ComputedVisibility,
@@ -77,32 +76,23 @@ pub fn extract_text2d_sprite(
7776
&ComputedVisibility,
7877
&Text,
7978
&TextLayoutInfo,
79+
&Anchor,
8080
&GlobalTransform,
81-
&Text2dSize,
8281
)>,
8382
>,
8483
) {
8584
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
8685

87-
for (entity, computed_visibility, text, text_layout_info, text_transform, calculated_size) in
86+
for (entity, computed_visibility, text, text_layout_info, anchor, text_transform) in
8887
text2d_query.iter()
8988
{
9089
if !computed_visibility.is_visible() {
9190
continue;
9291
}
93-
let (width, height) = (calculated_size.size.x, calculated_size.size.y);
9492

9593
let text_glyphs = &text_layout_info.glyphs;
96-
let alignment_offset = match text.alignment.vertical {
97-
VerticalAlign::Top => Vec3::new(0.0, -height, 0.0),
98-
VerticalAlign::Center => Vec3::new(0.0, -height * 0.5, 0.0),
99-
VerticalAlign::Bottom => Vec3::ZERO,
100-
} + match text.alignment.horizontal {
101-
HorizontalAlign::Left => Vec3::ZERO,
102-
HorizontalAlign::Center => Vec3::new(-width * 0.5, 0.0, 0.0),
103-
HorizontalAlign::Right => Vec3::new(-width, 0.0, 0.0),
104-
};
105-
94+
let text_anchor = anchor.as_vec() * Vec2::new(1., -1.) - 0.5;
95+
let alignment_offset = text_layout_info.size * text_anchor;
10696
let mut color = Color::WHITE;
10797
let mut current_section = usize::MAX;
10898
for text_glyph in text_glyphs {
@@ -120,10 +110,9 @@ pub fn extract_text2d_sprite(
120110
let index = text_glyph.atlas_info.glyph_index;
121111
let rect = Some(atlas.textures[index]);
122112

123-
let glyph_transform = Transform::from_translation(
124-
alignment_offset * scale_factor + text_glyph.position.extend(0.),
125-
);
126-
// NOTE: Should match `bevy_ui::render::extract_text_uinodes`
113+
let glyph_transform =
114+
Transform::from_translation((alignment_offset + text_glyph.position).extend(0.));
115+
127116
let transform = *text_transform
128117
* GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()))
129118
* glyph_transform;
@@ -167,24 +156,20 @@ pub fn update_text2d_layout(
167156
mut text_query: Query<(
168157
Entity,
169158
Ref<Text>,
170-
Option<&Text2dBounds>,
171-
&mut Text2dSize,
159+
&Text2dBounds,
172160
Option<&mut TextLayoutInfo>,
173161
)>,
174162
) {
175163
// We need to consume the entire iterator, hence `last`
176164
let factor_changed = scale_factor_changed.iter().last().is_some();
177165
let scale_factor = windows.scale_factor(WindowId::primary());
178166

179-
for (entity, text, maybe_bounds, mut calculated_size, text_layout_info) in &mut text_query {
167+
for (entity, text, bounds, text_layout_info) in &mut text_query {
180168
if factor_changed || text.is_changed() || queue.remove(&entity) {
181-
let text_bounds = match maybe_bounds {
182-
Some(bounds) => Vec2::new(
183-
scale_value(bounds.size.x, scale_factor),
184-
scale_value(bounds.size.y, scale_factor),
185-
),
186-
None => Vec2::new(f32::MAX, f32::MAX),
187-
};
169+
let text_bounds = Vec2::new(
170+
scale_value(bounds.size.x, scale_factor),
171+
scale_value(bounds.size.y, scale_factor),
172+
);
188173

189174
match text_pipeline.queue_text(
190175
&fonts,
@@ -207,18 +192,12 @@ pub fn update_text2d_layout(
207192
Err(e @ TextError::FailedToAddGlyph(_)) => {
208193
panic!("Fatal error when processing text: {e}.");
209194
}
210-
Ok(info) => {
211-
calculated_size.size = Vec2::new(
212-
scale_value(info.size.x, 1. / scale_factor),
213-
scale_value(info.size.y, 1. / scale_factor),
214-
);
215-
match text_layout_info {
216-
Some(mut t) => *t = info,
217-
None => {
218-
commands.entity(entity).insert(info);
219-
}
195+
Ok(info) => match text_layout_info {
196+
Some(mut t) => *t = info,
197+
None => {
198+
commands.entity(entity).insert(info);
220199
}
221-
}
200+
},
222201
}
223202
}
224203
}

0 commit comments

Comments
 (0)