Skip to content

Commit

Permalink
add spawn_text_block() extension method for Commands/EntityCommands
Browse files Browse the repository at this point in the history
  • Loading branch information
UkoeHB committed Oct 3, 2024
1 parent 3411081 commit 7431c56
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 53 deletions.
56 changes: 53 additions & 3 deletions crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use cosmic_text::{Buffer, Metrics};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;

use crate::{Font, TextLayoutInfo};
use crate::{Font, TextLayoutInfo, TextRoot};
pub use cosmic_text::{
self, FamilyOwned as FontFamily, Stretch as FontStretch, Style as FontStyle,
Weight as FontWeight,
Expand Down Expand Up @@ -279,13 +279,63 @@ pub enum FontSmoothing {
// SubpixelAntiAliased,
}

/// Provides convenience methods for constructing text blocks.
/// Provides convenience methods for spawning text blocks.
pub trait TextBuilderExt {
/// Spawns an entire text block including the root entity as a child of the current entity.
///
/// If no spans are provided then a default text entity will be spawned.
///
/// Returns an [`EntityCommands`] for the root entity.
fn spawn_text_block<R: TextRoot>(&mut self, spans: Vec<(String, TextStyle)>) -> EntityCommands;
}

impl TextBuilderExt for Commands<'_, '_> {
fn spawn_text_block<R: TextRoot>(
&mut self,
mut spans: Vec<(String, TextStyle)>,
) -> EntityCommands {
let mut spans = spans.drain(..);

// Root of the block.
let first = spans.next().unwrap_or_default();
let mut ec = self.spawn((R::from(first.0), first.1));

// Spans.
while let Some(next) = spans.next() {
ec.with_child((R::Span::from(next.0), next.1));
}

ec
}
}

impl TextBuilderExt for EntityCommands<'_> {
fn spawn_text_block<R: TextRoot>(
&mut self,
mut spans: Vec<(String, TextStyle)>,
) -> EntityCommands {
let mut spans = spans.drain(..);

// Root of the block.
let first = spans.next().unwrap_or_default();
let ec = self.with_child((R::from(first.0), first.1));

// Spans.
while let Some(next) = spans.next() {
ec.with_child((R::Span::from(next.0), next.1));
}

ec.reborrow()
}
}

/// Provides convenience methods for adding text spans to a text block.
pub trait TextSpanBuilderExt {
/// Adds a flat list of spans as children of the current entity.
fn with_spans<S: Component>(&mut self, spans: Vec<(S, TextStyle)>) -> &mut Self;
}

impl TextBuilderExt for EntityCommands<'_> {
impl TextSpanBuilderExt for EntityCommands<'_> {
fn with_spans<S: Component>(&mut self, mut spans: Vec<(S, TextStyle)>) -> &mut Self {
for (span, style) in spans.drain(..) {
self.with_child((span, style));
Expand Down
20 changes: 10 additions & 10 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::pipeline::CosmicFontSystem;
use crate::{
ComputedTextBlock, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, TextBlock,
TextBounds, TextError, TextLayoutInfo, TextPipeline, TextReader, TextSpanAccess, TextStyle,
TextWriter, YAxisOrientation,
TextBounds, TextError, TextLayoutInfo, TextPipeline, TextReader, TextRoot, TextSpanAccess,
TextStyle, TextWriter, YAxisOrientation,
};
use bevy_asset::Assets;
use bevy_color::LinearRgba;
Expand Down Expand Up @@ -100,6 +100,10 @@ impl Text2d {
}
}

impl TextRoot for Text2d {
type Span = TextSpan2d;
}

impl TextSpanAccess for Text2d {
fn read_span(&self) -> &str {
self.as_str()
Expand Down Expand Up @@ -156,7 +160,7 @@ world.spawn((
*/
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component, Default, Debug)]
#[require(TextStyle, Visibility(visibility_hidden), Transform)]
#[require(TextStyle)]
pub struct TextSpan2d(pub String);

impl TextSpan2d {
Expand Down Expand Up @@ -192,15 +196,11 @@ impl From<String> for TextSpan2d {
}
}

fn visibility_hidden() -> Visibility {
Visibility::Hidden
}

/// 2d alias for [`TextReader`].
pub type TextReader2d<'w, 's> = TextReader<'w, 's, Text2d, TextSpan2d>;
pub type TextReader2d<'w, 's> = TextReader<'w, 's, Text2d>;

/// 2d alias for [`TextWriter`].
pub type TextWriter2d<'w, 's> = TextWriter<'w, 's, Text2d, TextSpan2d>;
pub type TextWriter2d<'w, 's> = TextWriter<'w, 's, Text2d>;

/// This system extracts the sprites from the 2D text components and adds them to the
/// "render world".
Expand Down Expand Up @@ -313,7 +313,7 @@ pub fn update_text2d_layout(
&mut TextLayoutInfo,
&mut ComputedTextBlock,
)>,
mut text_reader: TextReader<Text2d, TextSpan2d>,
mut text_reader: TextReader2d,
mut font_system: ResMut<CosmicFontSystem>,
mut swash_cache: ResMut<SwashCache>,
) {
Expand Down
48 changes: 35 additions & 13 deletions crates/bevy_text/src/text_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ use bevy_hierarchy::Children;

use crate::TextStyle;

/// Helper trait for using the [`TextReader`] system param.
/// Helper trait for using the [`TextReader`] and [`TextWriter`] system params.
pub trait TextSpanAccess: Component {
/// Gets the text span's string.
fn read_span(&self) -> &str;
/// Gets mutable reference to the text span's string.
fn write_span(&mut self) -> &mut String;
}

/// Helper trait for the root text component in a text block.
pub trait TextRoot: TextSpanAccess + From<String> {
/// Component type for spans of text blocks starting with the root component.
type Span: TextSpanAccess + From<String>;
}

#[derive(Resource, Default)]
pub(crate) struct TextIterScratch {
stack: Vec<(&'static Children, usize)>,
Expand All @@ -40,15 +46,23 @@ impl TextIterScratch {
///
/// `R` is the root text component, and `S` is the text span component on children.
#[derive(SystemParam)]
pub struct TextReader<'w, 's, R: TextSpanAccess, S: TextSpanAccess> {
pub struct TextReader<'w, 's, R: TextRoot> {
scratch: ResMut<'w, TextIterScratch>,
roots: Query<'w, 's, (&'static R, &'static TextStyle, Option<&'static Children>)>,
spans: Query<'w, 's, (&'static S, &'static TextStyle, Option<&'static Children>)>,
spans: Query<
'w,
's,
(
&'static <R as TextRoot>::Span,
&'static TextStyle,
Option<&'static Children>,
),
>,
}

impl<'w, 's, R: TextSpanAccess, S: TextSpanAccess> TextReader<'w, 's, R, S> {
impl<'w, 's, R: TextRoot> TextReader<'w, 's, R> {
/// Returns an iterator over text spans in a text block, starting with the root entity.
pub fn iter<'a>(&'a mut self, root_entity: Entity) -> TextSpanIter<'a, R, S> {
pub fn iter<'a>(&'a mut self, root_entity: Entity) -> TextSpanIter<'a, R> {
let stack = self.scratch.take();

TextSpanIter {
Expand Down Expand Up @@ -99,16 +113,24 @@ impl<'w, 's, R: TextSpanAccess, S: TextSpanAccess> TextReader<'w, 's, R, S> {
/// Iterates all spans in a text block according to hierarchy traversal order.
/// Does *not* flatten interspersed ghost nodes. Only contiguous spans are traversed.
// TODO: Use this iterator design in UiChildrenIter to reduce allocations.
pub struct TextSpanIter<'a, R: TextSpanAccess, S: TextSpanAccess> {
pub struct TextSpanIter<'a, R: TextRoot> {
scratch: &'a mut TextIterScratch,
root_entity: Option<Entity>,
/// Stack of (children, next index into children).
stack: Vec<(&'a Children, usize)>,
roots: &'a Query<'a, 'a, (&'static R, &'static TextStyle, Option<&'static Children>)>,
spans: &'a Query<'a, 'a, (&'static S, &'static TextStyle, Option<&'static Children>)>,
spans: &'a Query<
'a,
'a,
(
&'static <R as TextRoot>::Span,
&'static TextStyle,
Option<&'static Children>,
),
>,
}

impl<'a, R: TextSpanAccess, S: TextSpanAccess> Iterator for TextSpanIter<'a, R, S> {
impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> {
/// Item = (entity in text block, hierarchy depth in the block, span text, span style).
type Item = (Entity, usize, &'a str, &'a TextStyle);
fn next(&mut self) -> Option<Self::Item> {
Expand Down Expand Up @@ -156,7 +178,7 @@ impl<'a, R: TextSpanAccess, S: TextSpanAccess> Iterator for TextSpanIter<'a, R,
}
}

impl<'a, R: TextSpanAccess, S: TextSpanAccess> Drop for TextSpanIter<'a, R, S> {
impl<'a, R: TextRoot> Drop for TextSpanIter<'a, R> {
fn drop(&mut self) {
// Return the internal stack.
let stack = std::mem::take(&mut self.stack);
Expand All @@ -168,14 +190,14 @@ impl<'a, R: TextSpanAccess, S: TextSpanAccess> Drop for TextSpanIter<'a, R, S> {
///
/// `R` is the root text component, and `S` is the text span component on children.
#[derive(SystemParam)]
pub struct TextWriter<'w, 's, R: TextSpanAccess, S: TextSpanAccess> {
pub struct TextWriter<'w, 's, R: TextRoot> {
scratch: ResMut<'w, TextIterScratch>,
roots: Query<'w, 's, (&'static mut R, &'static mut TextStyle), Without<S>>,
spans: Query<'w, 's, (&'static mut S, &'static mut TextStyle), Without<R>>,
roots: Query<'w, 's, (&'static mut R, &'static mut TextStyle), Without<<R as TextRoot>::Span>>,
spans: Query<'w, 's, (&'static mut <R as TextRoot>::Span, &'static mut TextStyle), Without<R>>,
children: Query<'w, 's, &'static Children>,
}

impl<'w, 's, R: TextSpanAccess, S: TextSpanAccess> TextWriter<'w, 's, R, S> {
impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> {
/// Gets a mutable reference to a text span within a text block at a specific index in the flattened span list.
pub fn get(
&mut self,
Expand Down
16 changes: 10 additions & 6 deletions crates/bevy_ui/src/widget/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use bevy_sprite::TextureAtlasLayout;
use bevy_text::{
scale_value, ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSets, LineBreak, SwashCache,
TextBlock, TextBounds, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextReader,
TextSpanAccess, TextStyle, TextWriter, YAxisOrientation,
TextRoot, TextSpanAccess, TextStyle, TextWriter, YAxisOrientation,
};
use bevy_transform::components::Transform;
use bevy_utils::{tracing::error, Entry};
Expand Down Expand Up @@ -116,6 +116,10 @@ impl TextNEW {
}
}

impl TextRoot for TextNEW {
type Span = TextSpan;
}

impl TextSpanAccess for TextNEW {
fn read_span(&self) -> &str {
self.as_str()
Expand Down Expand Up @@ -214,10 +218,10 @@ fn hidden_visibility() -> Visibility {
}

/// UI alias for [`TextReader`].
pub type UiTextReader<'w, 's> = TextReader<'w, 's, TextNEW, TextSpan>;
pub type UiTextReader<'w, 's> = TextReader<'w, 's, TextNEW>;

/// UI alias for [`TextWriter`].
pub type UiTextWriter<'w, 's> = TextWriter<'w, 's, TextNEW, TextSpan>;
pub type UiTextWriter<'w, 's> = TextWriter<'w, 's, TextNEW>;

/// Text measurement for UI layout. See [`NodeMeasure`].
pub struct TextMeasure {
Expand Down Expand Up @@ -350,7 +354,7 @@ pub fn measure_text_system(
),
With<Node>,
>,
mut text_reader: TextReader<TextNEW, TextSpan>,
mut text_reader: UiTextReader,
mut text_pipeline: ResMut<TextPipeline>,
mut font_system: ResMut<CosmicFontSystem>,
) {
Expand Down Expand Up @@ -413,7 +417,7 @@ fn queue_text(
mut text_flags: Mut<TextNodeFlags>,
text_layout_info: Mut<TextLayoutInfo>,
computed: &mut ComputedTextBlock,
text_reader: &mut TextReader<TextNEW, TextSpan>,
text_reader: &mut UiTextReader,
font_system: &mut CosmicFontSystem,
swash_cache: &mut SwashCache,
) {
Expand Down Expand Up @@ -493,7 +497,7 @@ pub fn text_system(
&mut ComputedTextBlock,
Option<&TargetCamera>,
)>,
mut text_reader: TextReader<TextNEW, TextSpan>,
mut text_reader: UiTextReader,
mut font_system: ResMut<CosmicFontSystem>,
mut swash_cache: ResMut<SwashCache>,
) {
Expand Down
35 changes: 14 additions & 21 deletions examples/stress_tests/text_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy::{
color::palettes::basic::{BLUE, YELLOW},
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
text::{LineBreak, TextBounds},
text::{LineBreak, TextBounds, TextBuilderExt},
window::{PresentMode, WindowResolution},
winit::{UpdateMode, WinitSettings},
};
Expand Down Expand Up @@ -63,26 +63,19 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
]
};

let [t1, p1] = make_spans(1);
commands
.spawn((
Text2d::new(t1.0),
t1.1,
TextBlock {
justify: JustifyText::Center,
linebreak: LineBreak::AnyCharacter,
..Default::default()
},
TextBounds::default(),
))
.with_children(|parent| {
parent.spawn((TextSpan2d(p1.0), p1.1));
for i in 2..=50 {
let [t, p] = make_spans(i);
parent.spawn((TextSpan2d(t.0), t.1));
parent.spawn((TextSpan2d(p.0), p.1));
}
});
let spans = (1..50)
.into_iter()
.flat_map(|i| make_spans(i).into_iter())
.collect();

commands.spawn_text_block::<Text2d>(spans).insert((
TextBlock {
justify: JustifyText::Center,
linebreak: LineBreak::AnyCharacter,
..Default::default()
},
TextBounds::default(),
));
}

// changing the bounds of the text will cause a recomputation
Expand Down

0 comments on commit 7431c56

Please sign in to comment.