Skip to content

Commit bd68ba1

Browse files
committed
make TextLayoutInfo a Component (#4460)
# Objective Make `TextLayoutInfo` more accessible as a component, rather than internal to `TextPipeline`. I am working on a plugin that manipulates these and there is no (mutable) access to them right now. ## Solution This changes `TextPipeline::queue_text` to return `TextLayoutInfo`'s rather than storing them in a map internally. `text2d_system` and `text_system` now take the returned `TextLayoutInfo` and store it as a component of the entity. I considered adding an accessor to `TextPipeline` (e.g. `get_glyphs_mut`) but this seems like it might be a little faster, and also has the added benefit of cleaning itself up when entities are removed. Right now nothing is ever removed from the glyphs map. ## Changelog Removed `DefaultTextPipeline`. `TextPipeline` no longer has a generic key type. `TextPipeline::queue_text` returns `TextLayoutInfo` directly. ## Migration Guide This might break a third-party crate? I could restore the orginal TextPipeline API as a wrapper around what's in this PR.
1 parent 1914696 commit bd68ba1

File tree

5 files changed

+137
-149
lines changed

5 files changed

+137
-149
lines changed

crates/bevy_text/src/lib.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,11 @@ pub mod prelude {
2828

2929
use bevy_app::prelude::*;
3030
use bevy_asset::AddAsset;
31-
use bevy_ecs::{entity::Entity, schedule::ParallelSystemDescriptorCoercion};
31+
use bevy_ecs::schedule::ParallelSystemDescriptorCoercion;
3232
use bevy_render::{RenderApp, RenderStage};
3333
use bevy_sprite::SpriteSystem;
3434
use bevy_window::ModifiesWindows;
3535

36-
pub type DefaultTextPipeline = TextPipeline<Entity>;
37-
3836
#[derive(Default)]
3937
pub struct TextPlugin;
4038

@@ -48,7 +46,7 @@ impl Plugin for TextPlugin {
4846
.register_type::<VerticalAlign>()
4947
.register_type::<HorizontalAlign>()
5048
.init_asset_loader::<FontLoader>()
51-
.insert_resource(DefaultTextPipeline::default())
49+
.insert_resource(TextPipeline::default())
5250
.add_system_to_stage(
5351
CoreStage::PostUpdate,
5452
update_text2d_layout.after(ModifiesWindows),

crates/bevy_text/src/pipeline.rs

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use std::hash::Hash;
2-
31
use ab_glyph::{PxScale, ScaleFont};
42
use bevy_asset::{Assets, Handle, HandleId};
3+
use bevy_ecs::component::Component;
54
use bevy_ecs::system::Resource;
65
use bevy_math::Vec2;
76
use bevy_render::texture::Image;
@@ -15,29 +14,22 @@ use crate::{
1514
TextAlignment, TextSection,
1615
};
1716

18-
#[derive(Resource)]
19-
pub struct TextPipeline<ID> {
17+
#[derive(Default, Resource)]
18+
pub struct TextPipeline {
2019
brush: GlyphBrush,
21-
glyph_map: HashMap<ID, TextLayoutInfo>,
2220
map_font_id: HashMap<HandleId, FontId>,
2321
}
2422

25-
impl<ID> Default for TextPipeline<ID> {
26-
fn default() -> Self {
27-
TextPipeline {
28-
brush: GlyphBrush::default(),
29-
glyph_map: Default::default(),
30-
map_font_id: Default::default(),
31-
}
32-
}
33-
}
34-
23+
/// Render information for a corresponding [`Text`](crate::Text) component.
24+
///
25+
/// Contains scaled glyphs and their size. Generated via [`TextPipeline::queue_text`].
26+
#[derive(Component, Clone, Default, Debug)]
3527
pub struct TextLayoutInfo {
3628
pub glyphs: Vec<PositionedGlyph>,
3729
pub size: Vec2,
3830
}
3931

40-
impl<ID: Hash + Eq> TextPipeline<ID> {
32+
impl TextPipeline {
4133
pub fn get_or_insert_font_id(&mut self, handle: &Handle<Font>, font: &Font) -> FontId {
4234
let brush = &mut self.brush;
4335
*self
@@ -46,14 +38,9 @@ impl<ID: Hash + Eq> TextPipeline<ID> {
4638
.or_insert_with(|| brush.add_font(handle.clone(), font.font.clone()))
4739
}
4840

49-
pub fn get_glyphs(&self, id: &ID) -> Option<&TextLayoutInfo> {
50-
self.glyph_map.get(id)
51-
}
52-
5341
#[allow(clippy::too_many_arguments)]
5442
pub fn queue_text(
5543
&mut self,
56-
id: ID,
5744
fonts: &Assets<Font>,
5845
sections: &[TextSection],
5946
scale_factor: f64,
@@ -62,7 +49,7 @@ impl<ID: Hash + Eq> TextPipeline<ID> {
6249
font_atlas_set_storage: &mut Assets<FontAtlasSet>,
6350
texture_atlases: &mut Assets<TextureAtlas>,
6451
textures: &mut Assets<Image>,
65-
) -> Result<(), TextError> {
52+
) -> Result<TextLayoutInfo, TextError> {
6653
let mut scaled_fonts = Vec::new();
6754
let sections = sections
6855
.iter()
@@ -90,14 +77,7 @@ impl<ID: Hash + Eq> TextPipeline<ID> {
9077
.compute_glyphs(&sections, bounds, text_alignment)?;
9178

9279
if section_glyphs.is_empty() {
93-
self.glyph_map.insert(
94-
id,
95-
TextLayoutInfo {
96-
glyphs: Vec::new(),
97-
size: Vec2::ZERO,
98-
},
99-
);
100-
return Ok(());
80+
return Ok(TextLayoutInfo::default());
10181
}
10282

10383
let mut min_x: f32 = std::f32::MAX;
@@ -125,8 +105,6 @@ impl<ID: Hash + Eq> TextPipeline<ID> {
125105
textures,
126106
)?;
127107

128-
self.glyph_map.insert(id, TextLayoutInfo { glyphs, size });
129-
130-
Ok(())
108+
Ok(TextLayoutInfo { glyphs, size })
131109
}
132110
}

crates/bevy_text/src/text2d.rs

Lines changed: 65 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use bevy_ecs::{
66
event::EventReader,
77
query::Changed,
88
reflect::ReflectComponent,
9-
system::{Local, Query, Res, ResMut},
9+
system::{Commands, Local, Query, Res, ResMut},
1010
};
1111
use bevy_math::{Vec2, Vec3};
1212
use bevy_reflect::Reflect;
@@ -22,7 +22,8 @@ use bevy_utils::HashSet;
2222
use bevy_window::{WindowId, WindowScaleFactorChanged, Windows};
2323

2424
use crate::{
25-
DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, VerticalAlign,
25+
Font, FontAtlasSet, HorizontalAlign, Text, TextError, TextLayoutInfo, TextPipeline,
26+
VerticalAlign,
2627
};
2728

2829
/// The calculated size of text drawn in 2D scene.
@@ -69,76 +70,75 @@ pub struct Text2dBundle {
6970
pub fn extract_text2d_sprite(
7071
mut extracted_sprites: ResMut<ExtractedSprites>,
7172
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
72-
text_pipeline: Extract<Res<DefaultTextPipeline>>,
7373
windows: Extract<Res<Windows>>,
7474
text2d_query: Extract<
7575
Query<(
7676
Entity,
7777
&ComputedVisibility,
7878
&Text,
79+
&TextLayoutInfo,
7980
&GlobalTransform,
8081
&Text2dSize,
8182
)>,
8283
>,
8384
) {
8485
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
8586

86-
for (entity, computed_visibility, text, text_transform, calculated_size) in text2d_query.iter()
87+
for (entity, computed_visibility, text, text_layout_info, text_transform, calculated_size) in
88+
text2d_query.iter()
8789
{
8890
if !computed_visibility.is_visible() {
8991
continue;
9092
}
9193
let (width, height) = (calculated_size.size.x, calculated_size.size.y);
9294

93-
if let Some(text_layout) = text_pipeline.get_glyphs(&entity) {
94-
let text_glyphs = &text_layout.glyphs;
95-
let alignment_offset = match text.alignment.vertical {
96-
VerticalAlign::Top => Vec3::new(0.0, -height, 0.0),
97-
VerticalAlign::Center => Vec3::new(0.0, -height * 0.5, 0.0),
98-
VerticalAlign::Bottom => Vec3::ZERO,
99-
} + match text.alignment.horizontal {
100-
HorizontalAlign::Left => Vec3::ZERO,
101-
HorizontalAlign::Center => Vec3::new(-width * 0.5, 0.0, 0.0),
102-
HorizontalAlign::Right => Vec3::new(-width, 0.0, 0.0),
103-
};
95+
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+
};
104105

105-
let mut color = Color::WHITE;
106-
let mut current_section = usize::MAX;
107-
for text_glyph in text_glyphs {
108-
if text_glyph.section_index != current_section {
109-
color = text.sections[text_glyph.section_index]
110-
.style
111-
.color
112-
.as_rgba_linear();
113-
current_section = text_glyph.section_index;
114-
}
115-
let atlas = texture_atlases
116-
.get(&text_glyph.atlas_info.texture_atlas)
117-
.unwrap();
118-
let handle = atlas.texture.clone_weak();
119-
let index = text_glyph.atlas_info.glyph_index as usize;
120-
let rect = Some(atlas.textures[index]);
106+
let mut color = Color::WHITE;
107+
let mut current_section = usize::MAX;
108+
for text_glyph in text_glyphs {
109+
if text_glyph.section_index != current_section {
110+
color = text.sections[text_glyph.section_index]
111+
.style
112+
.color
113+
.as_rgba_linear();
114+
current_section = text_glyph.section_index;
115+
}
116+
let atlas = texture_atlases
117+
.get(&text_glyph.atlas_info.texture_atlas)
118+
.unwrap();
119+
let handle = atlas.texture.clone_weak();
120+
let index = text_glyph.atlas_info.glyph_index as usize;
121+
let rect = Some(atlas.textures[index]);
121122

122-
let glyph_transform = Transform::from_translation(
123-
alignment_offset * scale_factor + text_glyph.position.extend(0.),
124-
);
125-
// NOTE: Should match `bevy_ui::render::extract_text_uinodes`
126-
let transform = *text_transform
127-
* GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()))
128-
* glyph_transform;
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`
127+
let transform = *text_transform
128+
* GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()))
129+
* glyph_transform;
129130

130-
extracted_sprites.sprites.push(ExtractedSprite {
131-
entity,
132-
transform,
133-
color,
134-
rect,
135-
custom_size: None,
136-
image_handle_id: handle.id,
137-
flip_x: false,
138-
flip_y: false,
139-
anchor: Anchor::Center.as_vec(),
140-
});
141-
}
131+
extracted_sprites.sprites.push(ExtractedSprite {
132+
entity,
133+
transform,
134+
color,
135+
rect,
136+
custom_size: None,
137+
image_handle_id: handle.id,
138+
flip_x: false,
139+
flip_y: false,
140+
anchor: Anchor::Center.as_vec(),
141+
});
142142
}
143143
}
144144
}
@@ -147,6 +147,7 @@ pub fn extract_text2d_sprite(
147147
/// This information is computed by the `TextPipeline` on insertion, then stored.
148148
#[allow(clippy::too_many_arguments)]
149149
pub fn update_text2d_layout(
150+
mut commands: Commands,
150151
// Text items which should be reprocessed again, generally when the font hasn't loaded yet.
151152
mut queue: Local<HashSet<Entity>>,
152153
mut textures: ResMut<Assets<Image>>,
@@ -155,20 +156,23 @@ pub fn update_text2d_layout(
155156
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
156157
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
157158
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
158-
mut text_pipeline: ResMut<DefaultTextPipeline>,
159+
mut text_pipeline: ResMut<TextPipeline>,
159160
mut text_query: Query<(
160161
Entity,
161162
Changed<Text>,
162163
&Text,
163164
Option<&Text2dBounds>,
164165
&mut Text2dSize,
166+
Option<&mut TextLayoutInfo>,
165167
)>,
166168
) {
167169
// We need to consume the entire iterator, hence `last`
168170
let factor_changed = scale_factor_changed.iter().last().is_some();
169171
let scale_factor = windows.scale_factor(WindowId::primary());
170172

171-
for (entity, text_changed, text, maybe_bounds, mut calculated_size) in &mut text_query {
173+
for (entity, text_changed, text, maybe_bounds, mut calculated_size, text_layout_info) in
174+
&mut text_query
175+
{
172176
if factor_changed || text_changed || queue.remove(&entity) {
173177
let text_bounds = match maybe_bounds {
174178
Some(bounds) => Vec2::new(
@@ -178,7 +182,6 @@ pub fn update_text2d_layout(
178182
None => Vec2::new(f32::MAX, f32::MAX),
179183
};
180184
match text_pipeline.queue_text(
181-
entity,
182185
&fonts,
183186
&text.sections,
184187
scale_factor,
@@ -196,14 +199,17 @@ pub fn update_text2d_layout(
196199
Err(e @ TextError::FailedToAddGlyph(_)) => {
197200
panic!("Fatal error when processing text: {}.", e);
198201
}
199-
Ok(()) => {
200-
let text_layout_info = text_pipeline.get_glyphs(&entity).expect(
201-
"Failed to get glyphs from the pipeline that have just been computed",
202-
);
202+
Ok(info) => {
203203
calculated_size.size = Vec2::new(
204-
scale_value(text_layout_info.size.x, 1. / scale_factor),
205-
scale_value(text_layout_info.size.y, 1. / scale_factor),
204+
scale_value(info.size.x, 1. / scale_factor),
205+
scale_value(info.size.y, 1. / scale_factor),
206206
);
207+
match text_layout_info {
208+
Some(mut t) => *t = info,
209+
None => {
210+
commands.entity(entity).insert(info);
211+
}
212+
}
207213
}
208214
}
209215
}

0 commit comments

Comments
 (0)