Skip to content

Commit 7431c56

Browse files
committed
add spawn_text_block() extension method for Commands/EntityCommands
1 parent 3411081 commit 7431c56

File tree

5 files changed

+122
-53
lines changed

5 files changed

+122
-53
lines changed

crates/bevy_text/src/text.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use cosmic_text::{Buffer, Metrics};
99
use serde::{Deserialize, Serialize};
1010
use smallvec::SmallVec;
1111

12-
use crate::{Font, TextLayoutInfo};
12+
use crate::{Font, TextLayoutInfo, TextRoot};
1313
pub use cosmic_text::{
1414
self, FamilyOwned as FontFamily, Stretch as FontStretch, Style as FontStyle,
1515
Weight as FontWeight,
@@ -279,13 +279,63 @@ pub enum FontSmoothing {
279279
// SubpixelAntiAliased,
280280
}
281281

282-
/// Provides convenience methods for constructing text blocks.
282+
/// Provides convenience methods for spawning text blocks.
283283
pub trait TextBuilderExt {
284+
/// Spawns an entire text block including the root entity as a child of the current entity.
285+
///
286+
/// If no spans are provided then a default text entity will be spawned.
287+
///
288+
/// Returns an [`EntityCommands`] for the root entity.
289+
fn spawn_text_block<R: TextRoot>(&mut self, spans: Vec<(String, TextStyle)>) -> EntityCommands;
290+
}
291+
292+
impl TextBuilderExt for Commands<'_, '_> {
293+
fn spawn_text_block<R: TextRoot>(
294+
&mut self,
295+
mut spans: Vec<(String, TextStyle)>,
296+
) -> EntityCommands {
297+
let mut spans = spans.drain(..);
298+
299+
// Root of the block.
300+
let first = spans.next().unwrap_or_default();
301+
let mut ec = self.spawn((R::from(first.0), first.1));
302+
303+
// Spans.
304+
while let Some(next) = spans.next() {
305+
ec.with_child((R::Span::from(next.0), next.1));
306+
}
307+
308+
ec
309+
}
310+
}
311+
312+
impl TextBuilderExt for EntityCommands<'_> {
313+
fn spawn_text_block<R: TextRoot>(
314+
&mut self,
315+
mut spans: Vec<(String, TextStyle)>,
316+
) -> EntityCommands {
317+
let mut spans = spans.drain(..);
318+
319+
// Root of the block.
320+
let first = spans.next().unwrap_or_default();
321+
let ec = self.with_child((R::from(first.0), first.1));
322+
323+
// Spans.
324+
while let Some(next) = spans.next() {
325+
ec.with_child((R::Span::from(next.0), next.1));
326+
}
327+
328+
ec.reborrow()
329+
}
330+
}
331+
332+
/// Provides convenience methods for adding text spans to a text block.
333+
pub trait TextSpanBuilderExt {
284334
/// Adds a flat list of spans as children of the current entity.
285335
fn with_spans<S: Component>(&mut self, spans: Vec<(S, TextStyle)>) -> &mut Self;
286336
}
287337

288-
impl TextBuilderExt for EntityCommands<'_> {
338+
impl TextSpanBuilderExt for EntityCommands<'_> {
289339
fn with_spans<S: Component>(&mut self, mut spans: Vec<(S, TextStyle)>) -> &mut Self {
290340
for (span, style) in spans.drain(..) {
291341
self.with_child((span, style));

crates/bevy_text/src/text2d.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::pipeline::CosmicFontSystem;
22
use crate::{
33
ComputedTextBlock, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, TextBlock,
4-
TextBounds, TextError, TextLayoutInfo, TextPipeline, TextReader, TextSpanAccess, TextStyle,
5-
TextWriter, YAxisOrientation,
4+
TextBounds, TextError, TextLayoutInfo, TextPipeline, TextReader, TextRoot, TextSpanAccess,
5+
TextStyle, TextWriter, YAxisOrientation,
66
};
77
use bevy_asset::Assets;
88
use bevy_color::LinearRgba;
@@ -100,6 +100,10 @@ impl Text2d {
100100
}
101101
}
102102

103+
impl TextRoot for Text2d {
104+
type Span = TextSpan2d;
105+
}
106+
103107
impl TextSpanAccess for Text2d {
104108
fn read_span(&self) -> &str {
105109
self.as_str()
@@ -156,7 +160,7 @@ world.spawn((
156160
*/
157161
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)]
158162
#[reflect(Component, Default, Debug)]
159-
#[require(TextStyle, Visibility(visibility_hidden), Transform)]
163+
#[require(TextStyle)]
160164
pub struct TextSpan2d(pub String);
161165

162166
impl TextSpan2d {
@@ -192,15 +196,11 @@ impl From<String> for TextSpan2d {
192196
}
193197
}
194198

195-
fn visibility_hidden() -> Visibility {
196-
Visibility::Hidden
197-
}
198-
199199
/// 2d alias for [`TextReader`].
200-
pub type TextReader2d<'w, 's> = TextReader<'w, 's, Text2d, TextSpan2d>;
200+
pub type TextReader2d<'w, 's> = TextReader<'w, 's, Text2d>;
201201

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

205205
/// This system extracts the sprites from the 2D text components and adds them to the
206206
/// "render world".
@@ -313,7 +313,7 @@ pub fn update_text2d_layout(
313313
&mut TextLayoutInfo,
314314
&mut ComputedTextBlock,
315315
)>,
316-
mut text_reader: TextReader<Text2d, TextSpan2d>,
316+
mut text_reader: TextReader2d,
317317
mut font_system: ResMut<CosmicFontSystem>,
318318
mut swash_cache: ResMut<SwashCache>,
319319
) {

crates/bevy_text/src/text_access.rs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ use bevy_hierarchy::Children;
66

77
use crate::TextStyle;
88

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

17+
/// Helper trait for the root text component in a text block.
18+
pub trait TextRoot: TextSpanAccess + From<String> {
19+
/// Component type for spans of text blocks starting with the root component.
20+
type Span: TextSpanAccess + From<String>;
21+
}
22+
1723
#[derive(Resource, Default)]
1824
pub(crate) struct TextIterScratch {
1925
stack: Vec<(&'static Children, usize)>,
@@ -40,15 +46,23 @@ impl TextIterScratch {
4046
///
4147
/// `R` is the root text component, and `S` is the text span component on children.
4248
#[derive(SystemParam)]
43-
pub struct TextReader<'w, 's, R: TextSpanAccess, S: TextSpanAccess> {
49+
pub struct TextReader<'w, 's, R: TextRoot> {
4450
scratch: ResMut<'w, TextIterScratch>,
4551
roots: Query<'w, 's, (&'static R, &'static TextStyle, Option<&'static Children>)>,
46-
spans: Query<'w, 's, (&'static S, &'static TextStyle, Option<&'static Children>)>,
52+
spans: Query<
53+
'w,
54+
's,
55+
(
56+
&'static <R as TextRoot>::Span,
57+
&'static TextStyle,
58+
Option<&'static Children>,
59+
),
60+
>,
4761
}
4862

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

5468
TextSpanIter {
@@ -99,16 +113,24 @@ impl<'w, 's, R: TextSpanAccess, S: TextSpanAccess> TextReader<'w, 's, R, S> {
99113
/// Iterates all spans in a text block according to hierarchy traversal order.
100114
/// Does *not* flatten interspersed ghost nodes. Only contiguous spans are traversed.
101115
// TODO: Use this iterator design in UiChildrenIter to reduce allocations.
102-
pub struct TextSpanIter<'a, R: TextSpanAccess, S: TextSpanAccess> {
116+
pub struct TextSpanIter<'a, R: TextRoot> {
103117
scratch: &'a mut TextIterScratch,
104118
root_entity: Option<Entity>,
105119
/// Stack of (children, next index into children).
106120
stack: Vec<(&'a Children, usize)>,
107121
roots: &'a Query<'a, 'a, (&'static R, &'static TextStyle, Option<&'static Children>)>,
108-
spans: &'a Query<'a, 'a, (&'static S, &'static TextStyle, Option<&'static Children>)>,
122+
spans: &'a Query<
123+
'a,
124+
'a,
125+
(
126+
&'static <R as TextRoot>::Span,
127+
&'static TextStyle,
128+
Option<&'static Children>,
129+
),
130+
>,
109131
}
110132

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

159-
impl<'a, R: TextSpanAccess, S: TextSpanAccess> Drop for TextSpanIter<'a, R, S> {
181+
impl<'a, R: TextRoot> Drop for TextSpanIter<'a, R> {
160182
fn drop(&mut self) {
161183
// Return the internal stack.
162184
let stack = std::mem::take(&mut self.stack);
@@ -168,14 +190,14 @@ impl<'a, R: TextSpanAccess, S: TextSpanAccess> Drop for TextSpanIter<'a, R, S> {
168190
///
169191
/// `R` is the root text component, and `S` is the text span component on children.
170192
#[derive(SystemParam)]
171-
pub struct TextWriter<'w, 's, R: TextSpanAccess, S: TextSpanAccess> {
193+
pub struct TextWriter<'w, 's, R: TextRoot> {
172194
scratch: ResMut<'w, TextIterScratch>,
173-
roots: Query<'w, 's, (&'static mut R, &'static mut TextStyle), Without<S>>,
174-
spans: Query<'w, 's, (&'static mut S, &'static mut TextStyle), Without<R>>,
195+
roots: Query<'w, 's, (&'static mut R, &'static mut TextStyle), Without<<R as TextRoot>::Span>>,
196+
spans: Query<'w, 's, (&'static mut <R as TextRoot>::Span, &'static mut TextStyle), Without<R>>,
175197
children: Query<'w, 's, &'static Children>,
176198
}
177199

178-
impl<'w, 's, R: TextSpanAccess, S: TextSpanAccess> TextWriter<'w, 's, R, S> {
200+
impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> {
179201
/// Gets a mutable reference to a text span within a text block at a specific index in the flattened span list.
180202
pub fn get(
181203
&mut self,

crates/bevy_ui/src/widget/text.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use bevy_sprite::TextureAtlasLayout;
2020
use bevy_text::{
2121
scale_value, ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSets, LineBreak, SwashCache,
2222
TextBlock, TextBounds, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextReader,
23-
TextSpanAccess, TextStyle, TextWriter, YAxisOrientation,
23+
TextRoot, TextSpanAccess, TextStyle, TextWriter, YAxisOrientation,
2424
};
2525
use bevy_transform::components::Transform;
2626
use bevy_utils::{tracing::error, Entry};
@@ -116,6 +116,10 @@ impl TextNEW {
116116
}
117117
}
118118

119+
impl TextRoot for TextNEW {
120+
type Span = TextSpan;
121+
}
122+
119123
impl TextSpanAccess for TextNEW {
120124
fn read_span(&self) -> &str {
121125
self.as_str()
@@ -214,10 +218,10 @@ fn hidden_visibility() -> Visibility {
214218
}
215219

216220
/// UI alias for [`TextReader`].
217-
pub type UiTextReader<'w, 's> = TextReader<'w, 's, TextNEW, TextSpan>;
221+
pub type UiTextReader<'w, 's> = TextReader<'w, 's, TextNEW>;
218222

219223
/// UI alias for [`TextWriter`].
220-
pub type UiTextWriter<'w, 's> = TextWriter<'w, 's, TextNEW, TextSpan>;
224+
pub type UiTextWriter<'w, 's> = TextWriter<'w, 's, TextNEW>;
221225

222226
/// Text measurement for UI layout. See [`NodeMeasure`].
223227
pub struct TextMeasure {
@@ -350,7 +354,7 @@ pub fn measure_text_system(
350354
),
351355
With<Node>,
352356
>,
353-
mut text_reader: TextReader<TextNEW, TextSpan>,
357+
mut text_reader: UiTextReader,
354358
mut text_pipeline: ResMut<TextPipeline>,
355359
mut font_system: ResMut<CosmicFontSystem>,
356360
) {
@@ -413,7 +417,7 @@ fn queue_text(
413417
mut text_flags: Mut<TextNodeFlags>,
414418
text_layout_info: Mut<TextLayoutInfo>,
415419
computed: &mut ComputedTextBlock,
416-
text_reader: &mut TextReader<TextNEW, TextSpan>,
420+
text_reader: &mut UiTextReader,
417421
font_system: &mut CosmicFontSystem,
418422
swash_cache: &mut SwashCache,
419423
) {
@@ -493,7 +497,7 @@ pub fn text_system(
493497
&mut ComputedTextBlock,
494498
Option<&TargetCamera>,
495499
)>,
496-
mut text_reader: TextReader<TextNEW, TextSpan>,
500+
mut text_reader: UiTextReader,
497501
mut font_system: ResMut<CosmicFontSystem>,
498502
mut swash_cache: ResMut<SwashCache>,
499503
) {

examples/stress_tests/text_pipeline.rs

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use bevy::{
66
color::palettes::basic::{BLUE, YELLOW},
77
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
88
prelude::*,
9-
text::{LineBreak, TextBounds},
9+
text::{LineBreak, TextBounds, TextBuilderExt},
1010
window::{PresentMode, WindowResolution},
1111
winit::{UpdateMode, WinitSettings},
1212
};
@@ -63,26 +63,19 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
6363
]
6464
};
6565

66-
let [t1, p1] = make_spans(1);
67-
commands
68-
.spawn((
69-
Text2d::new(t1.0),
70-
t1.1,
71-
TextBlock {
72-
justify: JustifyText::Center,
73-
linebreak: LineBreak::AnyCharacter,
74-
..Default::default()
75-
},
76-
TextBounds::default(),
77-
))
78-
.with_children(|parent| {
79-
parent.spawn((TextSpan2d(p1.0), p1.1));
80-
for i in 2..=50 {
81-
let [t, p] = make_spans(i);
82-
parent.spawn((TextSpan2d(t.0), t.1));
83-
parent.spawn((TextSpan2d(p.0), p.1));
84-
}
85-
});
66+
let spans = (1..50)
67+
.into_iter()
68+
.flat_map(|i| make_spans(i).into_iter())
69+
.collect();
70+
71+
commands.spawn_text_block::<Text2d>(spans).insert((
72+
TextBlock {
73+
justify: JustifyText::Center,
74+
linebreak: LineBreak::AnyCharacter,
75+
..Default::default()
76+
},
77+
TextBounds::default(),
78+
));
8679
}
8780

8881
// changing the bounds of the text will cause a recomputation

0 commit comments

Comments
 (0)