From eca2452b861d890e3bebeaf4a1977536c3df4c61 Mon Sep 17 00:00:00 2001 From: Al McElrath Date: Mon, 11 Apr 2022 12:14:33 -0700 Subject: [PATCH] make TextLayoutInfo a Component --- crates/bevy_text/src/lib.rs | 6 +- crates/bevy_text/src/pipeline.rs | 43 +++-------- crates/bevy_text/src/text2d.rs | 114 ++++++++++++++++-------------- crates/bevy_ui/src/render/mod.rs | 66 +++++++++-------- crates/bevy_ui/src/widget/text.rs | 32 +++++---- 5 files changed, 125 insertions(+), 136 deletions(-) diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 4dc1fc3b34070..65205c9a7df55 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; @@ -46,7 +44,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 bd7b0007cb82e..9112dd0b4d9dd 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_math::Vec2; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; @@ -14,28 +13,22 @@ use crate::{ TextAlignment, TextSection, }; -pub struct TextPipeline { +#[derive(Default)] +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 @@ -44,14 +37,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, @@ -60,7 +48,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() @@ -88,14 +76,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; @@ -123,8 +104,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 20b17fcfb493b..342dc539c72d8 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; @@ -21,7 +21,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. @@ -68,13 +69,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, )>, @@ -82,57 +83,56 @@ 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), + }; - for text_glyph in text_glyphs { - let color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - 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]); + for text_glyph in text_glyphs { + let color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + 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; + 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(), - }); - } + 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(), + }); } } } @@ -141,6 +141,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>, @@ -149,20 +150,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( @@ -172,7 +176,6 @@ pub fn update_text2d_layout( None => Vec2::new(f32::MAX, f32::MAX), }; match text_pipeline.queue_text( - entity, &fonts, &text.sections, scale_factor, @@ -190,14 +193,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 c336bbe0eaac5..bf6d750651af3 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::{Rect, 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; @@ -269,21 +269,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; } @@ -291,36 +291,34 @@ 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); - - for text_glyph in text_glyphs { - let color = text.sections[text_glyph.section_index].style.color; - 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 text_glyphs = &text_layout_info.glyphs; + let alignment_offset = (uinode.size / -2.0).extend(0.0); + + for text_glyph in text_glyphs { + let color = text.sections[text_glyph.section_index].style.color; + 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 895c4a6fdee48..d5e533f9e2b8c 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::{WindowId, 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>, @@ -45,11 +46,16 @@ pub fn text_system( windows: 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