Skip to content

Commit

Permalink
Remove VerticalAlign from TextAlignment (#6807)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
ickshonpe committed Jan 18, 2023
1 parent 4ff50f6 commit 9eefd7c
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 196 deletions.
2 changes: 1 addition & 1 deletion crates/bevy_sprite/src/sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct Sprite {

/// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform).
/// It defaults to `Anchor::Center`.
#[derive(Debug, Clone, Default, Reflect)]
#[derive(Component, Debug, Clone, Default, Reflect)]
#[doc(alias = "pivot")]
pub enum Anchor {
#[default]
Expand Down
3 changes: 1 addition & 2 deletions crates/bevy_text/src/glyph_brush.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ impl GlyphBrush {
..Default::default()
};
let section_glyphs = Layout::default()
.h_align(text_alignment.horizontal.into())
.v_align(text_alignment.vertical.into())
.h_align(text_alignment.into())
.calculate_glyphs(&self.fonts, &geom, sections);
Ok(section_glyphs)
}
Expand Down
8 changes: 2 additions & 6 deletions crates/bevy_text/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ pub use text2d::*;

pub mod prelude {
#[doc(hidden)]
pub use crate::{
Font, HorizontalAlign, Text, Text2dBundle, TextAlignment, TextError, TextSection,
TextStyle, VerticalAlign,
};
pub use crate::{Font, Text, Text2dBundle, TextAlignment, TextError, TextSection, TextStyle};
}

use bevy_app::prelude::*;
Expand Down Expand Up @@ -77,9 +74,8 @@ impl Plugin for TextPlugin {
.register_type::<TextSection>()
.register_type::<Vec<TextSection>>()
.register_type::<TextStyle>()
.register_type::<Text>()
.register_type::<TextAlignment>()
.register_type::<VerticalAlign>()
.register_type::<HorizontalAlign>()
.init_asset_loader::<FontLoader>()
.init_resource::<TextSettings>()
.init_resource::<FontAtlasWarning>()
Expand Down
125 changes: 23 additions & 102 deletions crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,36 @@ 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;

#[derive(Component, Debug, Default, Clone, Reflect)]
#[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,
}

impl Default for Text {
fn default() -> Self {
Self {
sections: Default::default(),
alignment: TextAlignment::Left,
}
}
}

impl Text {
/// Constructs a [`Text`] with a single section.
///
/// ```
/// # use bevy_asset::Handle;
/// # use bevy_render::color::Color;
/// # use bevy_text::{Font, Text, TextAlignment, TextStyle, HorizontalAlign, VerticalAlign};
/// # use bevy_text::{Font, Text, TextStyle, TextAlignment};
/// #
/// # let font_handle: Handle<Font> = Default::default();
/// #
Expand All @@ -42,12 +54,12 @@ impl Text {
/// color: Color::WHITE,
/// },
/// ) // You can still add an alignment.
/// .with_alignment(TextAlignment::CENTER);
/// .with_alignment(TextAlignment::Center);
/// ```
pub fn from_section(value: impl Into<String>, style: TextStyle) -> Self {
Self {
sections: vec![TextSection::new(value, style)],
alignment: Default::default(),
..default()
}
}

Expand Down Expand Up @@ -82,7 +94,7 @@ impl Text {
pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
Self {
sections: sections.into_iter().collect(),
alignment: Default::default(),
..default()
}
}

Expand Down Expand Up @@ -117,78 +129,10 @@ impl TextSection {
}
}

#[derive(Debug, Clone, Copy, Reflect)]
pub struct TextAlignment {
pub vertical: VerticalAlign,
pub horizontal: HorizontalAlign,
}

impl TextAlignment {
/// A [`TextAlignment`] set to the top-left.
pub const TOP_LEFT: Self = TextAlignment {
vertical: VerticalAlign::Top,
horizontal: HorizontalAlign::Left,
};

/// A [`TextAlignment`] set to the top-center.
pub const TOP_CENTER: Self = TextAlignment {
vertical: VerticalAlign::Top,
horizontal: HorizontalAlign::Center,
};

/// A [`TextAlignment`] set to the top-right.
pub const TOP_RIGHT: Self = TextAlignment {
vertical: VerticalAlign::Top,
horizontal: HorizontalAlign::Right,
};

/// A [`TextAlignment`] set to center the center-left.
pub const CENTER_LEFT: Self = TextAlignment {
vertical: VerticalAlign::Center,
horizontal: HorizontalAlign::Left,
};

/// A [`TextAlignment`] set to center on both axes.
pub const CENTER: Self = TextAlignment {
vertical: VerticalAlign::Center,
horizontal: HorizontalAlign::Center,
};

/// A [`TextAlignment`] set to the center-right.
pub const CENTER_RIGHT: Self = TextAlignment {
vertical: VerticalAlign::Center,
horizontal: HorizontalAlign::Right,
};

/// A [`TextAlignment`] set to the bottom-left.
pub const BOTTOM_LEFT: Self = TextAlignment {
vertical: VerticalAlign::Bottom,
horizontal: HorizontalAlign::Left,
};

/// A [`TextAlignment`] set to the bottom-center.
pub const BOTTOM_CENTER: Self = TextAlignment {
vertical: VerticalAlign::Bottom,
horizontal: HorizontalAlign::Center,
};

/// A [`TextAlignment`] set to the bottom-right.
pub const BOTTOM_RIGHT: Self = TextAlignment {
vertical: VerticalAlign::Bottom,
horizontal: HorizontalAlign::Right,
};
}

impl Default for TextAlignment {
fn default() -> Self {
TextAlignment::TOP_LEFT
}
}

/// Describes horizontal alignment preference for positioning & bounds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[reflect(Serialize, Deserialize)]
pub enum HorizontalAlign {
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,
Expand All @@ -200,35 +144,12 @@ pub enum HorizontalAlign {
Right,
}

impl From<HorizontalAlign> for glyph_brush_layout::HorizontalAlign {
fn from(val: HorizontalAlign) -> Self {
match val {
HorizontalAlign::Left => glyph_brush_layout::HorizontalAlign::Left,
HorizontalAlign::Center => glyph_brush_layout::HorizontalAlign::Center,
HorizontalAlign::Right => glyph_brush_layout::HorizontalAlign::Right,
}
}
}

/// Describes vertical alignment preference for positioning & bounds. Currently a placeholder
/// for future functionality.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[reflect(Serialize, Deserialize)]
pub enum VerticalAlign {
/// Characters/bounds start underneath the render position and progress downwards.
Top,
/// Characters/bounds center at the render position and progress outward equally.
Center,
/// Characters/bounds start above the render position and progress upward.
Bottom,
}

impl From<VerticalAlign> for glyph_brush_layout::VerticalAlign {
fn from(val: VerticalAlign) -> Self {
impl From<TextAlignment> for glyph_brush_layout::HorizontalAlign {
fn from(val: TextAlignment) -> Self {
match val {
VerticalAlign::Top => glyph_brush_layout::VerticalAlign::Top,
VerticalAlign::Center => glyph_brush_layout::VerticalAlign::Center,
VerticalAlign::Bottom => glyph_brush_layout::VerticalAlign::Bottom,
TextAlignment::Left => glyph_brush_layout::HorizontalAlign::Left,
TextAlignment::Center => glyph_brush_layout::HorizontalAlign::Center,
TextAlignment::Right => glyph_brush_layout::HorizontalAlign::Right,
}
}
}
Expand Down
81 changes: 30 additions & 51 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,10 @@ use bevy_utils::HashSet;
use bevy_window::{WindowId, WindowScaleFactorChanged, Windows};

use crate::{
Font, FontAtlasSet, FontAtlasWarning, HorizontalAlign, Text, TextError, TextLayoutInfo,
TextPipeline, TextSettings, VerticalAlign, YAxisOrientation,
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
TextSettings, YAxisOrientation,
};

/// The calculated size of text drawn in 2D scene.
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct Text2dSize {
pub size: Vec2,
}

/// 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`.
Expand All @@ -47,21 +40,27 @@ pub struct Text2dBounds {
}

impl Default for Text2dBounds {
#[inline]
fn default() -> Self {
Self {
size: Vec2::new(f32::MAX, f32::MAX),
}
Self::UNBOUNDED
}
}

impl Text2dBounds {
/// Unbounded text will not be truncated or wrapped.
pub const UNBOUNDED: Self = Self {
size: Vec2::splat(f32::INFINITY),
};
}

/// The bundle of components needed to draw text in a 2D scene via a 2D `Camera2dBundle`.
/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/text2d.rs)
#[derive(Bundle, Clone, Debug, Default)]
pub struct Text2dBundle {
pub text: Text,
pub text_anchor: Anchor,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub text_2d_size: Text2dSize,
pub text_2d_bounds: Text2dBounds,
pub visibility: Visibility,
pub computed_visibility: ComputedVisibility,
Expand All @@ -77,32 +76,23 @@ pub fn extract_text2d_sprite(
&ComputedVisibility,
&Text,
&TextLayoutInfo,
&Anchor,
&GlobalTransform,
&Text2dSize,
)>,
>,
) {
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;

for (entity, computed_visibility, text, text_layout_info, text_transform, calculated_size) in
for (entity, computed_visibility, text, text_layout_info, anchor, text_transform) in
text2d_query.iter()
{
if !computed_visibility.is_visible() {
continue;
}
let (width, height) = (calculated_size.size.x, calculated_size.size.y);

let text_glyphs = &text_layout_info.glyphs;
let alignment_offset = match text.alignment.vertical {
VerticalAlign::Top => Vec3::new(0.0, -height, 0.0),
VerticalAlign::Center => Vec3::new(0.0, -height * 0.5, 0.0),
VerticalAlign::Bottom => Vec3::ZERO,
} + match text.alignment.horizontal {
HorizontalAlign::Left => Vec3::ZERO,
HorizontalAlign::Center => Vec3::new(-width * 0.5, 0.0, 0.0),
HorizontalAlign::Right => Vec3::new(-width, 0.0, 0.0),
};

let text_anchor = anchor.as_vec() * Vec2::new(1., -1.) - 0.5;
let alignment_offset = text_layout_info.size * text_anchor;
let mut color = Color::WHITE;
let mut current_section = usize::MAX;
for text_glyph in text_glyphs {
Expand All @@ -120,10 +110,9 @@ pub fn extract_text2d_sprite(
let index = text_glyph.atlas_info.glyph_index;
let rect = Some(atlas.textures[index]);

let glyph_transform = Transform::from_translation(
alignment_offset * scale_factor + text_glyph.position.extend(0.),
);
// NOTE: Should match `bevy_ui::render::extract_text_uinodes`
let glyph_transform =
Transform::from_translation((alignment_offset + text_glyph.position).extend(0.));

let transform = *text_transform
* GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()))
* glyph_transform;
Expand Down Expand Up @@ -167,24 +156,20 @@ pub fn update_text2d_layout(
mut text_query: Query<(
Entity,
Ref<Text>,
Option<&Text2dBounds>,
&mut Text2dSize,
&Text2dBounds,
Option<&mut TextLayoutInfo>,
)>,
) {
// We need to consume the entire iterator, hence `last`
let factor_changed = scale_factor_changed.iter().last().is_some();
let scale_factor = windows.scale_factor(WindowId::primary());

for (entity, text, maybe_bounds, mut calculated_size, text_layout_info) in &mut text_query {
for (entity, text, bounds, text_layout_info) in &mut text_query {
if factor_changed || text.is_changed() || queue.remove(&entity) {
let text_bounds = match maybe_bounds {
Some(bounds) => Vec2::new(
scale_value(bounds.size.x, scale_factor),
scale_value(bounds.size.y, scale_factor),
),
None => Vec2::new(f32::MAX, f32::MAX),
};
let text_bounds = Vec2::new(
scale_value(bounds.size.x, scale_factor),
scale_value(bounds.size.y, scale_factor),
);

match text_pipeline.queue_text(
&fonts,
Expand All @@ -207,18 +192,12 @@ pub fn update_text2d_layout(
Err(e @ TextError::FailedToAddGlyph(_)) => {
panic!("Fatal error when processing text: {e}.");
}
Ok(info) => {
calculated_size.size = Vec2::new(
scale_value(info.size.x, 1. / scale_factor),
scale_value(info.size.y, 1. / scale_factor),
);
match text_layout_info {
Some(mut t) => *t = info,
None => {
commands.entity(entity).insert(info);
}
Ok(info) => match text_layout_info {
Some(mut t) => *t = info,
None => {
commands.entity(entity).insert(info);
}
}
},
}
}
}
Expand Down
Loading

0 comments on commit 9eefd7c

Please sign in to comment.