From 9f906fdc8b94ac039ea14b593db789aaba4f49b2 Mon Sep 17 00:00:00 2001 From: ira Date: Wed, 20 Jul 2022 14:14:29 +0000 Subject: [PATCH] Improve ergonomics and reduce boilerplate around creating text elements. (#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 :heart: >:D ## 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 --- .../bevy_render/src/mesh/mesh/conversions.rs | 2 +- crates/bevy_text/src/text.rs | 156 +++++++++--- crates/bevy_ui/src/entity.rs | 36 ++- examples/2d/text2d.rs | 22 +- .../external_source_external_thread.rs | 8 +- examples/ecs/state.rs | 20 +- examples/games/alien_cake_addict.rs | 34 ++- examples/games/breakout.rs | 45 ++-- examples/games/contributors.rs | 50 ++-- examples/games/game_menu.rs | 232 +++++++----------- examples/ios/src/lib.rs | 26 +- examples/scene/scene.rs | 18 +- examples/stress_tests/bevymark.rs | 74 +++--- .../transforms/global_vs_local_translation.rs | 23 +- examples/ui/button.rs | 20 +- examples/ui/font_atlas_debug.rs | 25 +- examples/ui/text.rs | 112 ++++----- examples/ui/text_debug.rs | 179 ++++++-------- examples/ui/transparency_ui.rs | 44 ++-- examples/ui/ui.rs | 72 +++--- examples/window/low_power.rs | 79 +++--- examples/window/scale_factor_override.rs | 18 +- 22 files changed, 618 insertions(+), 677 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/conversions.rs b/crates/bevy_render/src/mesh/mesh/conversions.rs index cb4c85db89..9ebaa06541 100644 --- a/crates/bevy_render/src/mesh/mesh/conversions.rs +++ b/crates/bevy_render/src/mesh/mesh/conversions.rs @@ -522,7 +522,7 @@ mod tests { Err(error) => error, }; assert_eq!( - format!("{}", error), + error.to_string(), "cannot convert VertexAttributeValues::Uint32x4 to alloc::vec::Vec" ); assert_eq!(format!("{:?}", error), diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 02f41ca0e9..4d1c695d84 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -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 = 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 with_section>( - value: S, - style: TextStyle, - alignment: TextAlignment, - ) -> Self { + pub fn from_section(value: impl Into, style: TextStyle) -> Self { Self { - sections: vec![TextSection { - value: value.into(), - style, - }], - alignment, + 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 = 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 from_sections(sections: impl IntoIterator) -> Self { + Self { + 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)] @@ -70,18 +99,89 @@ pub struct TextSection { pub style: TextStyle, } +impl TextSection { + /// Create a new [`TextSection`]. + pub fn new(value: impl Into, 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 } } diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 5345ee3dd6..3aa587f564 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -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 @@ -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, 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) -> 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 { diff --git a/examples/2d/text2d.rs b/examples/2d/text2d.rs index 5da6429627..b2edf872d2 100644 --- a/examples/2d/text2d.rs +++ b/examples/2d/text2d.rs @@ -31,30 +31,28 @@ fn setup(mut commands: Commands, asset_server: Res) { 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); @@ -70,16 +68,8 @@ fn setup(mut commands: Commands, asset_server: Res) { 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, diff --git a/examples/async_tasks/external_source_external_thread.rs b/examples/async_tasks/external_source_external_thread.rs index 76a78f519f..679d67d6a6 100644 --- a/examples/async_tasks/external_source_external_thread.rs +++ b/examples/async_tasks/external_source_external_thread.rs @@ -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, diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 03e4e7e0ad..f5a1bd161d 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -55,18 +55,14 @@ fn setup_menu(mut commands: Commands, asset_server: Res) { ..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 }); diff --git a/examples/games/alien_cake_addict.rs b/examples/games/alien_cake_addict.rs index 3d674382ea..f0a6b4e0c0 100644 --- a/examples/games/alien_cake_addict.rs +++ b/examples/games/alien_cake_addict.rs @@ -150,17 +150,16 @@ fn setup(mut commands: Commands, asset_server: Res, mut game: ResMu game.bonus.handle = asset_server.load("models/AlienCake/cakeBirthday.glb#Scene0"); // scoreboard - commands.spawn_bundle(TextBundle { - text: Text::with_section( + commands.spawn_bundle( + TextBundle::from_section( "Score:", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 40.0, color: Color::rgb(0.5, 0.5, 1.0), }, - Default::default(), - ), - style: Style { + ) + .with_style(Style { position_type: PositionType::Absolute, position: UiRect { top: Val::Px(5.0), @@ -168,9 +167,8 @@ fn setup(mut commands: Commands, asset_server: Res, mut game: ResMu ..default() }, ..default() - }, - ..default() - }); + }), + ); } // remove all entities that are not a camera @@ -383,17 +381,13 @@ fn display_score(mut commands: Commands, asset_server: Res, game: R ..default() }) .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section( - format!("Cake eaten: {}", game.cake_eaten), - TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 80.0, - color: Color::rgb(0.5, 0.5, 1.0), - }, - Default::default(), - ), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section( + format!("Cake eaten: {}", game.cake_eaten), + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 80.0, + color: Color::rgb(0.5, 0.5, 1.0), + }, + )); }); } diff --git a/examples/games/breakout.rs b/examples/games/breakout.rs index c43f74421c..2260ed91a5 100644 --- a/examples/games/breakout.rs +++ b/examples/games/breakout.rs @@ -216,29 +216,23 @@ fn setup(mut commands: Commands, asset_server: Res) { .insert(Velocity(INITIAL_BALL_DIRECTION.normalize() * BALL_SPEED)); // Scoreboard - commands.spawn_bundle(TextBundle { - text: Text { - sections: vec![ - TextSection { - value: "Score: ".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: SCOREBOARD_FONT_SIZE, - color: TEXT_COLOR, - }, + commands.spawn_bundle( + TextBundle::from_sections([ + TextSection::new( + "Score: ", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: SCOREBOARD_FONT_SIZE, + color: TEXT_COLOR, }, - TextSection { - value: "".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraMono-Medium.ttf"), - font_size: SCOREBOARD_FONT_SIZE, - color: SCORE_COLOR, - }, - }, - ], - ..default() - }, - style: Style { + ), + TextSection::from_style(TextStyle { + font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: SCOREBOARD_FONT_SIZE, + color: SCORE_COLOR, + }), + ]) + .with_style(Style { position_type: PositionType::Absolute, position: UiRect { top: SCOREBOARD_TEXT_PADDING, @@ -246,9 +240,8 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }, ..default() - }, - ..default() - }); + }), + ); // Walls commands.spawn_bundle(WallBundle::new(WallLocation::Left)); @@ -351,7 +344,7 @@ fn apply_velocity(mut query: Query<(&mut Transform, &Velocity)>) { fn update_scoreboard(scoreboard: Res, mut query: Query<&mut Text>) { let mut text = query.single_mut(); - text.sections[1].value = format!("{}", scoreboard.score); + text.sections[1].value = scoreboard.score.to_string(); } fn check_for_collisions( diff --git a/examples/games/contributors.rs b/examples/games/contributors.rs index c3842c4ec7..ff9ff1b32a 100644 --- a/examples/games/contributors.rs +++ b/examples/games/contributors.rs @@ -134,37 +134,27 @@ fn setup_contributor_selection(mut commands: Commands, asset_server: Res) { commands.spawn_bundle(Camera2dBundle::default()); - commands - .spawn() - .insert(ContributorDisplay) - .insert_bundle(TextBundle { - style: Style { - align_self: AlignSelf::FlexEnd, - ..default() - }, - text: Text { - sections: vec![ - TextSection { - value: "Contributor showcase".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 60.0, - color: Color::WHITE, - }, - }, - TextSection { - value: "".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 60.0, - color: Color::WHITE, - }, - }, - ], - ..default() - }, + commands.spawn().insert(ContributorDisplay).insert_bundle( + TextBundle::from_sections([ + TextSection::new( + "Contributor showcase", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 60.0, + color: Color::WHITE, + }, + ), + TextSection::from_style(TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 60.0, + color: Color::WHITE, + }), + ]) + .with_style(Style { + align_self: AlignSelf::FlexEnd, ..default() - }); + }), + ); } /// Finds the next contributor to display and selects the entity diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index d07f9cf484..7868929d43 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -165,58 +165,52 @@ mod game { .insert(OnGameScreen) .with_children(|parent| { // Display two lines of text, the second one with the current settings - parent.spawn_bundle(TextBundle { - style: Style { - margin: UiRect::all(Val::Px(50.0)), - ..default() - }, - text: Text::with_section( + parent.spawn_bundle( + TextBundle::from_section( "Will be back to the menu shortly...", TextStyle { font: font.clone(), font_size: 80.0, color: TEXT_COLOR, }, - Default::default(), - ), - ..default() - }); - parent.spawn_bundle(TextBundle { - style: Style { + ) + .with_style(Style { margin: UiRect::all(Val::Px(50.0)), ..default() - }, - text: Text { - sections: vec![ - TextSection { - value: format!("quality: {:?}", *display_quality), - style: TextStyle { - font: font.clone(), - font_size: 60.0, - color: Color::BLUE, - }, + }), + ); + parent.spawn_bundle( + TextBundle::from_sections([ + TextSection::new( + format!("quality: {:?}", *display_quality), + TextStyle { + font: font.clone(), + font_size: 60.0, + color: Color::BLUE, }, - TextSection { - value: " - ".to_string(), - style: TextStyle { - font: font.clone(), - font_size: 60.0, - color: TEXT_COLOR, - }, + ), + TextSection::new( + " - ", + TextStyle { + font: font.clone(), + font_size: 60.0, + color: TEXT_COLOR, }, - TextSection { - value: format!("volume: {:?}", *volume), - style: TextStyle { - font: font.clone(), - font_size: 60.0, - color: Color::GREEN, - }, + ), + TextSection::new( + format!("volume: {:?}", *volume), + TextStyle { + font: font.clone(), + font_size: 60.0, + color: Color::GREEN, }, - ], + ), + ]) + .with_style(Style { + margin: UiRect::all(Val::Px(50.0)), ..default() - }, - ..default() - }); + }), + ); }); // Spawn a 5 seconds timer to trigger going back to the menu commands.insert_resource(GameTimer(Timer::from_seconds(5.0, false))); @@ -432,22 +426,20 @@ mod menu { .insert(OnMainMenuScreen) .with_children(|parent| { // Display the game name - parent.spawn_bundle(TextBundle { - style: Style { - margin: UiRect::all(Val::Px(50.0)), - ..default() - }, - text: Text::with_section( + parent.spawn_bundle( + TextBundle::from_section( "Bevy Game Menu UI", TextStyle { font: font.clone(), font_size: 80.0, color: TEXT_COLOR, }, - Default::default(), - ), - ..default() - }); + ) + .with_style(Style { + margin: UiRect::all(Val::Px(50.0)), + ..default() + }), + ); // Display three buttons for each action available from the main menu: // - new game @@ -467,14 +459,10 @@ mod menu { image: UiImage(icon), ..default() }); - parent.spawn_bundle(TextBundle { - text: Text::with_section( - "New Game", - button_text_style.clone(), - Default::default(), - ), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section( + "New Game", + button_text_style.clone(), + )); }); parent .spawn_bundle(ButtonBundle { @@ -490,14 +478,10 @@ mod menu { image: UiImage(icon), ..default() }); - parent.spawn_bundle(TextBundle { - text: Text::with_section( - "Settings", - button_text_style.clone(), - Default::default(), - ), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section( + "Settings", + button_text_style.clone(), + )); }); parent .spawn_bundle(ButtonBundle { @@ -513,10 +497,7 @@ mod menu { image: UiImage(icon), ..default() }); - parent.spawn_bundle(TextBundle { - text: Text::with_section("Quit", button_text_style, Default::default()), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section("Quit", button_text_style)); }); }); } @@ -529,6 +510,7 @@ mod menu { align_items: AlignItems::Center, ..default() }; + let button_text_style = TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 40.0, @@ -548,55 +530,25 @@ mod menu { }) .insert(OnSettingsMenuScreen) .with_children(|parent| { - // Display two buttons for the submenus - parent - .spawn_bundle(ButtonBundle { - style: button_style.clone(), - color: NORMAL_BUTTON.into(), - ..default() - }) - .insert(MenuButtonAction::SettingsDisplay) - .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section( - "Display", + for (action, text) in [ + (MenuButtonAction::SettingsDisplay, "Display"), + (MenuButtonAction::SettingsSound, "Sound"), + (MenuButtonAction::BackToMainMenu, "Back"), + ] { + parent + .spawn_bundle(ButtonBundle { + style: button_style.clone(), + color: NORMAL_BUTTON.into(), + ..default() + }) + .insert(action) + .with_children(|parent| { + parent.spawn_bundle(TextBundle::from_section( + text, button_text_style.clone(), - Default::default(), - ), - ..default() + )); }); - }); - parent - .spawn_bundle(ButtonBundle { - style: button_style.clone(), - color: NORMAL_BUTTON.into(), - ..default() - }) - .insert(MenuButtonAction::SettingsSound) - .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section( - "Sound", - button_text_style.clone(), - Default::default(), - ), - ..default() - }); - }); - // Display the back button to return to the main menu screen - parent - .spawn_bundle(ButtonBundle { - style: button_style, - color: NORMAL_BUTTON.into(), - ..default() - }) - .insert(MenuButtonAction::BackToMainMenu) - .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section("Back", button_text_style, Default::default()), - ..default() - }); - }); + } }); } @@ -644,14 +596,10 @@ mod menu { }) .with_children(|parent| { // Display a label for the current setting - parent.spawn_bundle(TextBundle { - text: Text::with_section( - "Display Quality", - button_text_style.clone(), - Default::default(), - ), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section( + "Display Quality", + button_text_style.clone(), + )); // Display a button for each possible value for quality_setting in [ DisplayQuality::Low, @@ -667,14 +615,10 @@ mod menu { ..default() }); entity.insert(quality_setting).with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section( - format!("{:?}", quality_setting), - button_text_style.clone(), - Default::default(), - ), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section( + format!("{quality_setting:?}"), + button_text_style.clone(), + )); }); if *display_quality == quality_setting { entity.insert(SelectedOption); @@ -690,10 +634,7 @@ mod menu { }) .insert(MenuButtonAction::BackToSettings) .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section("Back", button_text_style, Default::default()), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section("Back", button_text_style)); }); }); } @@ -739,14 +680,10 @@ mod menu { ..default() }) .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section( - "Volume", - button_text_style.clone(), - Default::default(), - ), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section( + "Volume", + button_text_style.clone(), + )); for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] { let mut entity = parent.spawn_bundle(ButtonBundle { style: Style { @@ -770,10 +707,7 @@ mod menu { }) .insert(MenuButtonAction::BackToSettings) .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section("Back", button_text_style, Default::default()), - ..default() - }); + parent.spawn_bundle(TextBundle::from_section("Back", button_text_style)); }); }); } diff --git a/examples/ios/src/lib.rs b/examples/ios/src/lib.rs index ed03ad1bbc..3240598424 100644 --- a/examples/ios/src/lib.rs +++ b/examples/ios/src/lib.rs @@ -108,23 +108,17 @@ fn setup_scene( ..default() }) .with_children(|b| { - b.spawn_bundle(TextBundle { - text: Text { - sections: vec![TextSection { - value: "Test Button".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 30.0, - color: Color::BLACK, - }, - }], - alignment: TextAlignment { - vertical: VerticalAlign::Center, - horizontal: HorizontalAlign::Center, + b.spawn_bundle( + TextBundle::from_section( + "Test Button", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 30.0, + color: Color::BLACK, }, - }, - ..default() - }); + ) + .with_text_alignment(TextAlignment::CENTER), + ); }); } diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index 4b499a1b81..66689e2a8b 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -105,20 +105,18 @@ fn save_scene_system(world: &mut World) { // text example. fn infotext_system(mut commands: Commands, asset_server: Res) { commands.spawn_bundle(Camera2dBundle::default()); - commands.spawn_bundle(TextBundle { - style: Style { - align_self: AlignSelf::FlexEnd, - ..default() - }, - text: Text::with_section( + commands.spawn_bundle( + TextBundle::from_section( "Nothing to see in this window! Check the console output!", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 50.0, color: Color::WHITE, }, - Default::default(), - ), - ..default() - }); + ) + .with_style(Style { + align_self: AlignSelf::FlexEnd, + ..default() + }), + ); } diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index bf3955428b..249e506ce0 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -96,45 +96,36 @@ fn setup(mut commands: Commands, asset_server: Res) { commands.spawn_bundle(Camera2dBundle::default()); commands - .spawn_bundle(TextBundle { - text: Text { - sections: vec![ - TextSection { - value: "Bird Count: ".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 40.0, - color: Color::rgb(0.0, 1.0, 0.0), - }, + .spawn_bundle( + TextBundle::from_sections([ + TextSection::new( + "Bird Count: ", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 40.0, + color: Color::rgb(0.0, 1.0, 0.0), }, - TextSection { - value: "".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 40.0, - color: Color::rgb(0.0, 1.0, 1.0), - }, + ), + TextSection::from_style(TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 40.0, + color: Color::rgb(0.0, 1.0, 1.0), + }), + TextSection::new( + "\nAverage FPS: ", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 40.0, + color: Color::rgb(0.0, 1.0, 0.0), }, - TextSection { - value: "\nAverage FPS: ".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 40.0, - color: Color::rgb(0.0, 1.0, 0.0), - }, - }, - TextSection { - value: "".to_string(), - style: TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 40.0, - color: Color::rgb(0.0, 1.0, 1.0), - }, - }, - ], - ..default() - }, - style: Style { + ), + TextSection::from_style(TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 40.0, + color: Color::rgb(0.0, 1.0, 1.0), + }), + ]) + .with_style(Style { position_type: PositionType::Absolute, position: UiRect { top: Val::Px(5.0), @@ -142,9 +133,8 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }, ..default() - }, - ..default() - }) + }), + ) .insert(StatsText); commands.insert_resource(BirdTexture(texture)); @@ -265,12 +255,12 @@ fn counter_system( let mut text = query.single_mut(); if counter.is_changed() { - text.sections[1].value = format!("{}", counter.count); + text.sections[1].value = counter.count.to_string(); } if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) { if let Some(average) = fps.average() { - text.sections[3].value = format!("{:.2}", average); + text.sections[3].value = format!("{average:.2}"); } }; } diff --git a/examples/transforms/global_vs_local_translation.rs b/examples/transforms/global_vs_local_translation.rs index 815cdab22f..5bbaa445ee 100644 --- a/examples/transforms/global_vs_local_translation.rs +++ b/examples/transforms/global_vs_local_translation.rs @@ -101,25 +101,18 @@ fn setup( }); // Add text to explain inputs and what is happening. - commands.spawn_bundle(TextBundle { - text: Text::with_section( - "Press the arrow keys to move the cubes. Toggle movement for yellow (1), red (2) and green (3) cubes via number keys. + commands.spawn_bundle(TextBundle::from_section( + "Press the arrow keys to move the cubes. Toggle movement for yellow (1), red (2) and green (3) cubes via number keys. Notice how the green cube will translate further in respect to the yellow in contrast to the red cube. This is due to the use of its LocalTransform that is relative to the yellow cubes transform instead of the GlobalTransform as in the case of the red cube. The red cube is moved through its GlobalTransform and thus is unaffected by the yellows transform.", - TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 22.0, - color: Color::WHITE, - }, - TextAlignment { - horizontal: HorizontalAlign::Left, - ..default() - }, - ), - ..default() - }); + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 22.0, + color: Color::WHITE, + }, + )); } // This system will move all cubes that are marked as ChangeGlobal according to their global transform. diff --git a/examples/ui/button.rs b/examples/ui/button.rs index 4a8ea34f11..8007ee86c0 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -62,17 +62,13 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }) .with_children(|parent| { - parent.spawn_bundle(TextBundle { - text: Text::with_section( - "Button", - 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( + "Button", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 40.0, + color: Color::rgb(0.9, 0.9, 0.9), + }, + )); }); } diff --git a/examples/ui/font_atlas_debug.rs b/examples/ui/font_atlas_debug.rs index 71e9808cde..4cb3862422 100644 --- a/examples/ui/font_atlas_debug.rs +++ b/examples/ui/font_atlas_debug.rs @@ -67,8 +67,9 @@ fn text_update_system(mut state: ResMut, time: Res