Skip to content

Commit

Permalink
Improve ergonomics and reduce boilerplate around creating text elemen…
Browse files Browse the repository at this point in the history
…ts. (#5343)

# Objective

Creating UI elements is very boilerplate-y with lots of indentation.
This PR aims to reduce boilerplate around creating text elements.

## Changelog

* Renamed `Text::with_section` to `from_section`.
  It no longer takes a `TextAlignment` as argument, as the vast majority of cases left it `Default::default()`.
* Added `Text::from_sections` which creates a `Text` from a list of `TextSections`.
  Reduces line-count and reduces indentation by one level.
* Added `Text::with_alignment`.
  A builder style method for setting the `TextAlignment` of a `Text`.
* Added `TextSection::new`.
  Does not reduce line count, but reduces character count and made it easier to read. No more `.to_string()` calls!
* Added `TextSection::from_style` which creates an empty `TextSection` with a style.
  No more empty strings! Reduces indentation.
* Added `TextAlignment::CENTER` and friends.
* Added methods to `TextBundle`. `from_section`, `from_sections`, `with_text_alignment` and `with_style`.

## Note for reviewers.
Because of the nature of these changes I recommend setting diff view to 'split'.
~~Look for the book icon~~ cog in the top-left of the Files changed tab.

Have fun reviewing ❤️
<sup> >:D </sup>

## Migration Guide

`Text::with_section` was renamed to `from_section` and no longer takes a `TextAlignment` as argument.
Use `with_alignment` to set the alignment instead.

Co-authored-by: devil-ira <[email protected]>
  • Loading branch information
tim-blackbird and tim-blackbird committed Jul 20, 2022
1 parent 35f99a6 commit 9f906fd
Show file tree
Hide file tree
Showing 22 changed files with 616 additions and 675 deletions.
2 changes: 1 addition & 1 deletion crates/bevy_render/src/mesh/mesh/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ mod tests {
Err(error) => error,
};
assert_eq!(
format!("{}", error),
error.to_string(),
"cannot convert VertexAttributeValues::Uint32x4 to alloc::vec::Vec<u32>"
);
assert_eq!(format!("{:?}", error),
Expand Down
156 changes: 128 additions & 28 deletions crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,83 @@ pub struct Text {
}

impl Text {
/// Constructs a [`Text`] with (initially) one section.
/// Constructs a [`Text`] with a single section.
///
/// ```
/// # use bevy_asset::{AssetServer, Handle};
/// # use bevy_asset::Handle;
/// # use bevy_render::color::Color;
/// # use bevy_text::{Font, Text, TextAlignment, TextStyle, HorizontalAlign, VerticalAlign};
/// #
/// # let font_handle: Handle<Font> = Default::default();
/// #
/// // basic usage
/// let hello_world = Text::with_section(
/// "hello world!".to_string(),
/// // 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(),
/// font_size: 60.0,
/// color: Color::WHITE,
/// },
/// TextAlignment {
/// vertical: VerticalAlign::Center,
/// horizontal: HorizontalAlign::Center,
/// },
/// );
///
/// let hello_bevy = Text::with_section(
/// // accepts a String or any type that converts into a String, such as &str
/// let hello_bevy = Text::from_section(
/// "hello bevy!",
/// TextStyle {
/// font: font_handle,
/// font_size: 60.0,
/// color: Color::WHITE,
/// },
/// // you can still use Default
/// Default::default(),
/// );
/// ) // You can still add an alignment.
/// .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(),
}
}

/// Constructs a [`Text`] from a list of sections.
///
/// ```
/// # use bevy_asset::Handle;
/// # use bevy_render::color::Color;
/// # 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(),
/// font_size: 60.0,
/// color: Color::BLUE,
/// },
/// ),
/// TextSection::new(
/// "World!",
/// TextStyle {
/// font: font_handle,
/// font_size: 60.0,
/// color: Color::RED,
/// },
/// ),
/// ]);
/// ```
pub fn with_section<S: Into<String>>(
value: S,
style: TextStyle,
alignment: TextAlignment,
) -> Self {
pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
Self {
sections: vec![TextSection {
value: value.into(),
style,
}],
alignment,
sections: sections.into_iter().collect(),
alignment: Default::default(),
}
}

/// Returns this [`Text`] with a new [`TextAlignment`].
pub const fn with_alignment(mut self, alignment: TextAlignment) -> Self {
self.alignment = alignment;
self
}
}

#[derive(Debug, Default, Clone, FromReflect, Reflect)]
Expand All @@ -70,18 +99,89 @@ pub struct TextSection {
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,
}
}
}

#[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 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 {
vertical: VerticalAlign::Top,
horizontal: HorizontalAlign::Left,
}
TextAlignment::TOP_LEFT
}
}

Expand Down
36 changes: 35 additions & 1 deletion crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use bevy_render::{
camera::Camera, extract_component::ExtractComponent, prelude::ComputedVisibility,
view::Visibility,
};
use bevy_text::Text;
use bevy_text::{Text, TextAlignment, TextSection, TextStyle};
use bevy_transform::prelude::{GlobalTransform, Transform};

/// The basic UI node
Expand Down Expand Up @@ -89,6 +89,40 @@ pub struct TextBundle {
pub computed_visibility: ComputedVisibility,
}

impl TextBundle {
/// Create a [`TextBundle`] from a single section.
///
/// See [`Text::from_section`] for usage.
pub fn from_section(value: impl Into<String>, style: TextStyle) -> Self {
Self {
text: Text::from_section(value, style),
..Default::default()
}
}

/// Create a [`TextBundle`] from a list of sections.
///
/// See [`Text::from_sections`] for usage.
pub fn from_sections(sections: impl IntoIterator<Item = TextSection>) -> Self {
Self {
text: Text::from_sections(sections),
..Default::default()
}
}

/// Returns this [`TextBundle`] with a new [`TextAlignment`] on [`Text`].
pub const fn with_text_alignment(mut self, alignment: TextAlignment) -> Self {
self.text.alignment = alignment;
self
}

/// Returns this [`TextBundle`] with a new [`Style`].
pub const fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
}

impl Default for TextBundle {
fn default() -> Self {
TextBundle {
Expand Down
22 changes: 6 additions & 16 deletions examples/2d/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,28 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
font_size: 60.0,
color: Color::WHITE,
};
let text_alignment = TextAlignment {
vertical: VerticalAlign::Center,
horizontal: HorizontalAlign::Center,
};
let text_alignment = TextAlignment::CENTER;
// 2d camera
commands.spawn_bundle(Camera2dBundle::default());
// Demonstrate changing translation
commands
.spawn_bundle(Text2dBundle {
text: Text::with_section("translation", text_style.clone(), text_alignment),
text: Text::from_section("translation", text_style.clone())
.with_alignment(text_alignment),
..default()
})
.insert(AnimateTranslation);
// Demonstrate changing rotation
commands
.spawn_bundle(Text2dBundle {
text: Text::with_section("rotation", text_style.clone(), text_alignment),
text: Text::from_section("rotation", text_style.clone()).with_alignment(text_alignment),
..default()
})
.insert(AnimateRotation);
// Demonstrate changing scale
commands
.spawn_bundle(Text2dBundle {
text: Text::with_section("scale", text_style.clone(), text_alignment),
text: Text::from_section("scale", text_style.clone()).with_alignment(text_alignment),
..default()
})
.insert(AnimateScale);
Expand All @@ -70,16 +68,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
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: Text::from_section("this text wraps in the box", text_style),
text_2d_bounds: Text2dBounds {
// Wrap text in the rectangle
size: box_size,
Expand Down
8 changes: 3 additions & 5 deletions examples/async_tasks/external_source_external_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,11 @@ fn spawn_text(
font_size: 20.0,
color: Color::WHITE,
};
let text_alignment = TextAlignment {
vertical: VerticalAlign::Center,
horizontal: HorizontalAlign::Center,
};

for (per_frame, event) in reader.iter().enumerate() {
commands.spawn_bundle(Text2dBundle {
text: Text::with_section(format!("{}", event.0), text_style.clone(), text_alignment),
text: Text::from_section(event.0.to_string(), text_style.clone())
.with_alignment(TextAlignment::CENTER),
transform: Transform::from_xyz(
per_frame as f32 * 100.0 + rand::thread_rng().gen_range(-40.0..40.0),
300.0,
Expand Down
20 changes: 8 additions & 12 deletions examples/ecs/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,14 @@ fn setup_menu(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
})
.with_children(|parent| {
parent.spawn_bundle(TextBundle {
text: Text::with_section(
"Play",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::rgb(0.9, 0.9, 0.9),
},
Default::default(),
),
..default()
});
parent.spawn_bundle(TextBundle::from_section(
"Play",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::rgb(0.9, 0.9, 0.9),
},
));
})
.id();
commands.insert_resource(MenuData { button_entity });
Expand Down
Loading

0 comments on commit 9f906fd

Please sign in to comment.