From f36aa72999be1a64ee980642cfd65559a5dd6fac Mon Sep 17 00:00:00 2001 From: Zoey Bush Date: Mon, 24 Jun 2024 15:49:04 -0700 Subject: [PATCH] [bevy_text] Add offset to TextSection This makes it possible to shift text (such as for animations), while still laying it out with ab_glyph. --- crates/bevy_text/src/pipeline.rs | 20 +++++++++++++++++--- crates/bevy_text/src/text.rs | 20 ++++++++++++++++++-- examples/asset/multi_asset_sync.rs | 1 + examples/input/text_input.rs | 6 ++++++ examples/stress_tests/many_glyphs.rs | 1 + examples/stress_tests/text_pipeline.rs | 2 ++ examples/tools/gamepad_viewer.rs | 5 +++++ examples/ui/text_wrap_debug.rs | 1 + 8 files changed, 51 insertions(+), 5 deletions(-) diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 298857997e8969..5f63113c2c870a 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -56,6 +56,12 @@ impl TextPipeline { y_axis_orientation: YAxisOrientation, ) -> Result { let mut scaled_fonts = Vec::with_capacity(sections.len()); + let glyph_offsets: Vec<_> = sections + .iter() + .flat_map(|section| { + std::iter::repeat(section.offset).take(section.value.chars().count()) + }) + .collect(); let sections = sections .iter() .map(|section| { @@ -77,9 +83,17 @@ impl TextPipeline { }) .collect::, _>>()?; - let section_glyphs = - self.brush - .compute_glyphs(§ions, bounds, text_alignment, linebreak_behavior)?; + let section_glyphs: Vec<_> = self + .brush + .compute_glyphs(§ions, bounds, text_alignment, linebreak_behavior)? + .into_iter() + .zip(glyph_offsets) + .map(|(mut g, offset)| { + g.glyph.position.x += offset.x; + g.glyph.position.y += offset.y; + g + }) + .collect(); if section_glyphs.is_empty() { return Ok(TextLayoutInfo::default()); diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 5d1fce2d0d8e01..389147325fa28a 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -1,6 +1,7 @@ use bevy_asset::Handle; use bevy_color::Color; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; +use bevy_math::Vec2; use bevy_reflect::prelude::*; use bevy_utils::default; use serde::{Deserialize, Serialize}; @@ -106,10 +107,21 @@ impl Text { } } -#[derive(Debug, Default, Clone, Reflect)] +#[derive(Debug, Clone, Reflect)] pub struct TextSection { pub value: String, pub style: TextStyle, + pub offset: Vec2, +} + +impl Default for TextSection { + fn default() -> Self { + Self { + value: Default::default(), + style: Default::default(), + offset: Vec2::new(0., 0.), + } + } } impl TextSection { @@ -118,14 +130,18 @@ impl TextSection { Self { value: value.into(), style, + + ..Default::default() } } /// 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, + + value: String::new(), + offset: Vec2::new(0., 0.), } } } diff --git a/examples/asset/multi_asset_sync.rs b/examples/asset/multi_asset_sync.rs index db2306ae759181..04a87ebc80c235 100644 --- a/examples/asset/multi_asset_sync.rs +++ b/examples/asset/multi_asset_sync.rs @@ -189,6 +189,7 @@ fn setup_ui(mut commands: Commands) { color: Color::BLACK, ..Default::default() }, + ..Default::default() }], justify: JustifyText::Right, ..Default::default() diff --git a/examples/input/text_input.rs b/examples/input/text_input.rs index 2adfad7cf43997..4d65f8df877d47 100644 --- a/examples/input/text_input.rs +++ b/examples/input/text_input.rs @@ -43,6 +43,7 @@ fn setup_scene(mut commands: Commands, asset_server: Res) { font: font.clone_weak(), ..default() }, + ..default() }, TextSection { value: "false\n".to_string(), @@ -51,6 +52,7 @@ fn setup_scene(mut commands: Commands, asset_server: Res) { font_size: 30.0, ..default() }, + ..default() }, TextSection { value: "IME Active: ".to_string(), @@ -58,6 +60,7 @@ fn setup_scene(mut commands: Commands, asset_server: Res) { font: font.clone_weak(), ..default() }, + ..default() }, TextSection { value: "false\n".to_string(), @@ -66,6 +69,7 @@ fn setup_scene(mut commands: Commands, asset_server: Res) { font_size: 30.0, ..default() }, + ..default() }, TextSection { value: "click to toggle IME, press return to start a new line\n\n".to_string(), @@ -74,6 +78,7 @@ fn setup_scene(mut commands: Commands, asset_server: Res) { font_size: 18.0, ..default() }, + ..default() }, TextSection { value: "".to_string(), @@ -82,6 +87,7 @@ fn setup_scene(mut commands: Commands, asset_server: Res) { font_size: 25.0, ..default() }, + ..default() }, ]) .with_style(Style { diff --git a/examples/stress_tests/many_glyphs.rs b/examples/stress_tests/many_glyphs.rs index ca4bc6db28c534..8a439823273b42 100644 --- a/examples/stress_tests/many_glyphs.rs +++ b/examples/stress_tests/many_glyphs.rs @@ -52,6 +52,7 @@ fn setup(mut commands: Commands) { font_size: 4., ..default() }, + ..default(), }], justify: JustifyText::Left, linebreak_behavior: BreakLineOn::AnyCharacter, diff --git a/examples/stress_tests/text_pipeline.rs b/examples/stress_tests/text_pipeline.rs index 716d867d0d4e53..e79d33faeab151 100644 --- a/examples/stress_tests/text_pipeline.rs +++ b/examples/stress_tests/text_pipeline.rs @@ -49,6 +49,7 @@ fn spawn(mut commands: Commands, asset_server: Res) { font_size: (4 + i % 10) as f32, color: BLUE.into(), }, + ..default() }, TextSection { value: "pipeline".repeat(i), @@ -57,6 +58,7 @@ fn spawn(mut commands: Commands, asset_server: Res) { font_size: (4 + i % 11) as f32, color: YELLOW.into(), }, + ..default() }, ] }) diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index cfcb51529fbb67..feadbfa9d31f45 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -321,14 +321,17 @@ fn setup_sticks( TextSection { value: format!("{:.3}", 0.), style: style.clone(), + ..default() }, TextSection { value: ", ".to_string(), style: style.clone(), + ..default() }, TextSection { value: format!("{:.3}", 0.), style, + ..default() }, ]), text_anchor: Anchor::BottomCenter, @@ -424,10 +427,12 @@ fn setup_connected(mut commands: Commands) { TextSection { value: "Connected Gamepads:\n".to_string(), style: text_style.clone(), + ..default() }, TextSection { value: "None".to_string(), style: text_style, + ..default() }, ]), style: Style { diff --git a/examples/ui/text_wrap_debug.rs b/examples/ui/text_wrap_debug.rs index 724e1e0eb97abf..e56e37b27cc1f6 100644 --- a/examples/ui/text_wrap_debug.rs +++ b/examples/ui/text_wrap_debug.rs @@ -124,6 +124,7 @@ fn spawn(mut commands: Commands, asset_server: Res) { sections: vec![TextSection { value: message.clone(), style: text_style.clone(), + ..Default::default() }], justify: JustifyText::Left, linebreak_behavior,