diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 36d727d408..f8ff0c5896 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -28,13 +28,11 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_ecs::{entity::Entity, schedule::ParallelSystemDescriptorCoercion}; +use bevy_ecs::schedule::ParallelSystemDescriptorCoercion; use bevy_render::{RenderApp, RenderStage}; use bevy_sprite::SpriteSystem; use bevy_window::ModifiesWindows; -pub type DefaultTextPipeline = TextPipeline; - #[derive(Default)] pub struct TextPlugin; @@ -48,7 +46,7 @@ impl Plugin for TextPlugin { .register_type::() .register_type::() .init_asset_loader::() - .insert_resource(DefaultTextPipeline::default()) + .insert_resource(TextPipeline::default()) .add_system_to_stage( CoreStage::PostUpdate, update_text2d_layout.after(ModifiesWindows), diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index ea72b965e6..d06f24d897 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -1,7 +1,6 @@ -use std::hash::Hash; - use ab_glyph::{PxScale, ScaleFont}; use bevy_asset::{Assets, Handle, HandleId}; +use bevy_ecs::component::Component; use bevy_ecs::system::Resource; use bevy_math::Vec2; use bevy_render::texture::Image; @@ -15,29 +14,22 @@ use crate::{ TextAlignment, TextSection, }; -#[derive(Resource)] -pub struct TextPipeline { +#[derive(Default, Resource)] +pub struct TextPipeline { brush: GlyphBrush, - glyph_map: HashMap, map_font_id: HashMap, } -impl Default for TextPipeline { - fn default() -> Self { - TextPipeline { - brush: GlyphBrush::default(), - glyph_map: Default::default(), - map_font_id: Default::default(), - } - } -} - +/// Render information for a corresponding [`Text`](crate::Text) component. +/// +/// Contains scaled glyphs and their size. Generated via [`TextPipeline::queue_text`]. +#[derive(Component, Clone, Default, Debug)] pub struct TextLayoutInfo { pub glyphs: Vec, pub size: Vec2, } -impl TextPipeline { +impl TextPipeline { pub fn get_or_insert_font_id(&mut self, handle: &Handle, font: &Font) -> FontId { let brush = &mut self.brush; *self @@ -46,14 +38,9 @@ impl TextPipeline { .or_insert_with(|| brush.add_font(handle.clone(), font.font.clone())) } - pub fn get_glyphs(&self, id: &ID) -> Option<&TextLayoutInfo> { - self.glyph_map.get(id) - } - #[allow(clippy::too_many_arguments)] pub fn queue_text( &mut self, - id: ID, fonts: &Assets, sections: &[TextSection], scale_factor: f64, @@ -62,7 +49,7 @@ impl TextPipeline { font_atlas_set_storage: &mut Assets, texture_atlases: &mut Assets, textures: &mut Assets, - ) -> Result<(), TextError> { + ) -> Result { let mut scaled_fonts = Vec::new(); let sections = sections .iter() @@ -90,14 +77,7 @@ impl TextPipeline { .compute_glyphs(§ions, bounds, text_alignment)?; if section_glyphs.is_empty() { - self.glyph_map.insert( - id, - TextLayoutInfo { - glyphs: Vec::new(), - size: Vec2::ZERO, - }, - ); - return Ok(()); + return Ok(TextLayoutInfo::default()); } let mut min_x: f32 = std::f32::MAX; @@ -125,8 +105,6 @@ impl TextPipeline { textures, )?; - self.glyph_map.insert(id, TextLayoutInfo { glyphs, size }); - - Ok(()) + Ok(TextLayoutInfo { glyphs, size }) } } diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index dd4a5b63f5..2b5cb821b1 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ event::EventReader, query::Changed, reflect::ReflectComponent, - system::{Local, Query, Res, ResMut}, + system::{Commands, Local, Query, Res, ResMut}, }; use bevy_math::{Vec2, Vec3}; use bevy_reflect::Reflect; @@ -22,7 +22,8 @@ use bevy_utils::HashSet; use bevy_window::{WindowId, WindowScaleFactorChanged, Windows}; use crate::{ - DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, VerticalAlign, + Font, FontAtlasSet, HorizontalAlign, Text, TextError, TextLayoutInfo, TextPipeline, + VerticalAlign, }; /// The calculated size of text drawn in 2D scene. @@ -69,13 +70,13 @@ pub struct Text2dBundle { pub fn extract_text2d_sprite( mut extracted_sprites: ResMut, texture_atlases: Extract>>, - text_pipeline: Extract>, windows: Extract>, text2d_query: Extract< Query<( Entity, &ComputedVisibility, &Text, + &TextLayoutInfo, &GlobalTransform, &Text2dSize, )>, @@ -83,62 +84,61 @@ pub fn extract_text2d_sprite( ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, computed_visibility, text, text_transform, calculated_size) in text2d_query.iter() + for (entity, computed_visibility, text, text_layout_info, text_transform, calculated_size) in + text2d_query.iter() { if !computed_visibility.is_visible() { continue; } let (width, height) = (calculated_size.size.x, calculated_size.size.y); - if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { - let text_glyphs = &text_layout.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_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 mut color = Color::WHITE; - let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; - } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let handle = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index as usize; - 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 transform = *text_transform - * GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())) - * glyph_transform; - - extracted_sprites.sprites.push(ExtractedSprite { - entity, - transform, - color, - rect, - custom_size: None, - image_handle_id: handle.id, - flip_x: false, - flip_y: false, - anchor: Anchor::Center.as_vec(), - }); + let mut color = Color::WHITE; + let mut current_section = usize::MAX; + for text_glyph in text_glyphs { + if text_glyph.section_index != current_section { + color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + current_section = text_glyph.section_index; } + let atlas = texture_atlases + .get(&text_glyph.atlas_info.texture_atlas) + .unwrap(); + let handle = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + 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 transform = *text_transform + * GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())) + * glyph_transform; + + extracted_sprites.sprites.push(ExtractedSprite { + entity, + transform, + color, + rect, + custom_size: None, + image_handle_id: handle.id, + flip_x: false, + flip_y: false, + anchor: Anchor::Center.as_vec(), + }); } } } @@ -147,6 +147,7 @@ pub fn extract_text2d_sprite( /// This information is computed by the `TextPipeline` on insertion, then stored. #[allow(clippy::too_many_arguments)] pub fn update_text2d_layout( + mut commands: Commands, // Text items which should be reprocessed again, generally when the font hasn't loaded yet. mut queue: Local>, mut textures: ResMut>, @@ -155,20 +156,23 @@ pub fn update_text2d_layout( mut scale_factor_changed: EventReader, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, - mut text_pipeline: ResMut, + mut text_pipeline: ResMut, mut text_query: Query<( Entity, Changed, &Text, Option<&Text2dBounds>, &mut Text2dSize, + 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_changed, text, maybe_bounds, mut calculated_size) in &mut text_query { + for (entity, text_changed, text, maybe_bounds, mut calculated_size, text_layout_info) in + &mut text_query + { if factor_changed || text_changed || queue.remove(&entity) { let text_bounds = match maybe_bounds { Some(bounds) => Vec2::new( @@ -178,7 +182,6 @@ pub fn update_text2d_layout( None => Vec2::new(f32::MAX, f32::MAX), }; match text_pipeline.queue_text( - entity, &fonts, &text.sections, scale_factor, @@ -196,14 +199,17 @@ pub fn update_text2d_layout( Err(e @ TextError::FailedToAddGlyph(_)) => { panic!("Fatal error when processing text: {}.", e); } - Ok(()) => { - let text_layout_info = text_pipeline.get_glyphs(&entity).expect( - "Failed to get glyphs from the pipeline that have just been computed", - ); + Ok(info) => { calculated_size.size = Vec2::new( - scale_value(text_layout_info.size.x, 1. / scale_factor), - scale_value(text_layout_info.size.y, 1. / scale_factor), + 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); + } + } } } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 4c3f602f54..24fa992dc3 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -24,7 +24,7 @@ use bevy_render::{ Extract, RenderApp, RenderStage, }; use bevy_sprite::{SpriteAssetEvents, TextureAtlas}; -use bevy_text::{DefaultTextPipeline, Text}; +use bevy_text::{Text, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; use bevy_utils::FloatOrd; use bevy_utils::HashMap; @@ -272,21 +272,21 @@ pub fn extract_default_ui_camera_view( pub fn extract_text_uinodes( mut extracted_uinodes: ResMut, texture_atlases: Extract>>, - text_pipeline: Extract>, windows: Extract>, uinode_query: Extract< Query<( - Entity, &Node, &GlobalTransform, &Text, + &TextLayoutInfo, &ComputedVisibility, Option<&CalculatedClip>, )>, >, ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, uinode, global_transform, text, visibility, clip) in uinode_query.iter() { + for (uinode, global_transform, text, text_layout_info, visibility, clip) in uinode_query.iter() + { if !visibility.is_visible() { continue; } @@ -294,44 +294,42 @@ pub fn extract_text_uinodes( if uinode.size == Vec2::ZERO { continue; } - if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { - let text_glyphs = &text_layout.glyphs; - let alignment_offset = (uinode.size / -2.0).extend(0.0); + let text_glyphs = &text_layout_info.glyphs; + let alignment_offset = (uinode.size / -2.0).extend(0.0); - let mut color = Color::WHITE; - let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; - } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let texture = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index as usize; - let rect = atlas.textures[index]; - let atlas_size = Some(atlas.size); - - // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` - let extracted_transform = global_transform.compute_matrix() - * Mat4::from_scale(Vec3::splat(scale_factor.recip())) - * Mat4::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); - - extracted_uinodes.uinodes.push(ExtractedUiNode { - transform: extracted_transform, - color, - rect, - image: texture, - atlas_size, - clip: clip.map(|clip| clip.clip), - }); + let mut color = Color::WHITE; + let mut current_section = usize::MAX; + for text_glyph in text_glyphs { + if text_glyph.section_index != current_section { + color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + current_section = text_glyph.section_index; } + let atlas = texture_atlases + .get(&text_glyph.atlas_info.texture_atlas) + .unwrap(); + let texture = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + let rect = atlas.textures[index]; + let atlas_size = Some(atlas.size); + + // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` + let extracted_transform = global_transform.compute_matrix() + * Mat4::from_scale(Vec3::splat(scale_factor.recip())) + * Mat4::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); + + extracted_uinodes.uinodes.push(ExtractedUiNode { + transform: extracted_transform, + color, + rect, + image: texture, + atlas_size, + clip: clip.map(|clip| clip.clip), + }); } } } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 6e25817b9d..77ed556a47 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -3,12 +3,12 @@ use bevy_asset::Assets; use bevy_ecs::{ entity::Entity, query::{Changed, Or, With}, - system::{Local, ParamSet, Query, Res, ResMut}, + system::{Commands, Local, ParamSet, Query, Res, ResMut}, }; use bevy_math::Vec2; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; -use bevy_text::{DefaultTextPipeline, Font, FontAtlasSet, Text, TextError}; +use bevy_text::{Font, FontAtlasSet, Text, TextError, TextLayoutInfo, TextPipeline}; use bevy_window::Windows; #[derive(Debug, Default)] @@ -38,6 +38,7 @@ pub fn text_constraint(min_size: Val, size: Val, max_size: Val, scale_factor: f6 /// This information is computed by the `TextPipeline` on insertion, then stored. #[allow(clippy::too_many_arguments)] pub fn text_system( + mut commands: Commands, mut queued_text: Local, mut last_scale_factor: Local, mut textures: ResMut>, @@ -46,11 +47,16 @@ pub fn text_system( ui_scale: Res, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, - mut text_pipeline: ResMut, + mut text_pipeline: ResMut, mut text_queries: ParamSet<( Query, Changed