Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_text/src/text_access.rs
9358 views
1
use bevy_color::Color;
2
use bevy_ecs::{
3
component::Mutable,
4
prelude::*,
5
system::{Query, SystemParam},
6
};
7
8
use crate::{LineHeight, TextColor, TextFont, TextSpan};
9
10
/// Helper trait for using the [`TextReader`] and [`TextWriter`] system params.
11
pub trait TextSpanAccess: Component<Mutability = Mutable> {
12
/// Gets the text span's string.
13
fn read_span(&self) -> &str;
14
/// Gets mutable reference to the text span's string.
15
fn write_span(&mut self) -> &mut String;
16
}
17
18
/// Helper trait for the root text component in a text block.
19
pub trait TextRoot: TextSpanAccess + From<String> {}
20
21
/// Helper trait for the text span components in a text block.
22
pub trait TextSpanComponent: TextSpanAccess + From<String> {}
23
24
/// Scratch buffer used to store intermediate state when iterating over text spans.
25
#[derive(Resource, Default)]
26
pub struct TextIterScratch {
27
stack: Vec<(&'static Children, usize)>,
28
}
29
30
impl TextIterScratch {
31
fn take<'a>(&mut self) -> Vec<(&'a Children, usize)> {
32
core::mem::take(&mut self.stack)
33
.into_iter()
34
.map(|_| -> (&Children, usize) { unreachable!() })
35
.collect()
36
}
37
38
fn recover(&mut self, mut stack: Vec<(&Children, usize)>) {
39
stack.clear();
40
self.stack = stack
41
.into_iter()
42
.map(|_| -> (&'static Children, usize) { unreachable!() })
43
.collect();
44
}
45
}
46
47
/// System parameter for reading text spans in a text block.
48
///
49
/// `R` is the root text component.
50
#[derive(SystemParam)]
51
pub struct TextReader<'w, 's, R: TextRoot> {
52
// This is a local to avoid system ambiguities when TextReaders run in parallel.
53
scratch: Local<'s, TextIterScratch>,
54
roots: Query<
55
'w,
56
's,
57
(
58
&'static R,
59
&'static TextFont,
60
&'static TextColor,
61
&'static LineHeight,
62
Option<&'static Children>,
63
),
64
>,
65
spans: Query<
66
'w,
67
's,
68
(
69
&'static TextSpan,
70
&'static TextFont,
71
&'static TextColor,
72
&'static LineHeight,
73
Option<&'static Children>,
74
),
75
>,
76
}
77
78
impl<'w, 's, R: TextRoot> TextReader<'w, 's, R> {
79
/// Returns an iterator over text spans in a text block, starting with the root entity.
80
pub fn iter(&mut self, root_entity: Entity) -> TextSpanIter<'_, R> {
81
let stack = self.scratch.take();
82
83
TextSpanIter {
84
scratch: &mut self.scratch,
85
root_entity: Some(root_entity),
86
stack,
87
roots: &self.roots,
88
spans: &self.spans,
89
}
90
}
91
92
/// Gets a text span within a text block at a specific index in the flattened span list.
93
pub fn get(
94
&mut self,
95
root_entity: Entity,
96
index: usize,
97
) -> Option<(Entity, usize, &str, &TextFont, Color, LineHeight)> {
98
self.iter(root_entity).nth(index)
99
}
100
101
/// Gets the text value of a text span within a text block at a specific index in the flattened span list.
102
pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option<&str> {
103
self.get(root_entity, index)
104
.map(|(_, _, text, _, _, _)| text)
105
}
106
107
/// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list.
108
pub fn get_font(&mut self, root_entity: Entity, index: usize) -> Option<&TextFont> {
109
self.get(root_entity, index)
110
.map(|(_, _, _, font, _, _)| font)
111
}
112
113
/// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list.
114
pub fn get_color(&mut self, root_entity: Entity, index: usize) -> Option<Color> {
115
self.get(root_entity, index)
116
.map(|(_, _, _, _, color, _)| color)
117
}
118
119
/// Gets the [`LineHeight`] of a text span within a text block at a specific index in the flattened span list.
120
pub fn get_line_height(&mut self, root_entity: Entity, index: usize) -> Option<LineHeight> {
121
self.get(root_entity, index)
122
.map(|(_, _, _, _, _, line_height)| line_height)
123
}
124
125
/// Gets the text value of a text span within a text block at a specific index in the flattened span list.
126
///
127
/// Panics if there is no span at the requested index.
128
pub fn text(&mut self, root_entity: Entity, index: usize) -> &str {
129
self.get_text(root_entity, index).unwrap()
130
}
131
132
/// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list.
133
///
134
/// Panics if there is no span at the requested index.
135
pub fn font(&mut self, root_entity: Entity, index: usize) -> &TextFont {
136
self.get_font(root_entity, index).unwrap()
137
}
138
139
/// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list.
140
///
141
/// Panics if there is no span at the requested index.
142
pub fn color(&mut self, root_entity: Entity, index: usize) -> Color {
143
self.get_color(root_entity, index).unwrap()
144
}
145
146
/// Gets the [`LineHeight`] of a text span within a text block at a specific index in the flattened span list.
147
pub fn line_height(&mut self, root_entity: Entity, index: usize) -> LineHeight {
148
self.get_line_height(root_entity, index).unwrap()
149
}
150
}
151
152
/// Iterator returned by [`TextReader::iter`].
153
///
154
/// Iterates all spans in a text block according to hierarchy traversal order.
155
/// Does *not* flatten interspersed ghost nodes. Only contiguous spans are traversed.
156
// TODO: Use this iterator design in UiChildrenIter to reduce allocations.
157
pub struct TextSpanIter<'a, R: TextRoot> {
158
scratch: &'a mut TextIterScratch,
159
root_entity: Option<Entity>,
160
/// Stack of (children, next index into children).
161
stack: Vec<(&'a Children, usize)>,
162
roots: &'a Query<
163
'a,
164
'a,
165
(
166
&'static R,
167
&'static TextFont,
168
&'static TextColor,
169
&'static LineHeight,
170
Option<&'static Children>,
171
),
172
>,
173
spans: &'a Query<
174
'a,
175
'a,
176
(
177
&'static TextSpan,
178
&'static TextFont,
179
&'static TextColor,
180
&'static LineHeight,
181
Option<&'static Children>,
182
),
183
>,
184
}
185
186
impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> {
187
/// Item = (entity in text block, hierarchy depth in the block, span text, span style).
188
type Item = (Entity, usize, &'a str, &'a TextFont, Color, LineHeight);
189
fn next(&mut self) -> Option<Self::Item> {
190
// Root
191
if let Some(root_entity) = self.root_entity.take() {
192
if let Ok((text, text_font, color, line_height, maybe_children)) =
193
self.roots.get(root_entity)
194
{
195
if let Some(children) = maybe_children {
196
self.stack.push((children, 0));
197
}
198
return Some((
199
root_entity,
200
0,
201
text.read_span(),
202
text_font,
203
color.0,
204
*line_height,
205
));
206
}
207
return None;
208
}
209
210
// Span
211
loop {
212
let (children, idx) = self.stack.last_mut()?;
213
214
loop {
215
let Some(child) = children.get(*idx) else {
216
break;
217
};
218
219
// Increment to prep the next entity in this stack level.
220
*idx += 1;
221
222
let entity = *child;
223
let Ok((span, text_font, color, line_height, maybe_children)) =
224
self.spans.get(entity)
225
else {
226
continue;
227
};
228
229
let depth = self.stack.len();
230
if let Some(children) = maybe_children {
231
self.stack.push((children, 0));
232
}
233
return Some((
234
entity,
235
depth,
236
span.read_span(),
237
text_font,
238
color.0,
239
*line_height,
240
));
241
}
242
243
// All children at this stack entry have been iterated.
244
self.stack.pop();
245
}
246
}
247
}
248
249
impl<'a, R: TextRoot> Drop for TextSpanIter<'a, R> {
250
fn drop(&mut self) {
251
// Return the internal stack.
252
let stack = core::mem::take(&mut self.stack);
253
self.scratch.recover(stack);
254
}
255
}
256
257
/// System parameter for reading and writing text spans in a text block.
258
///
259
/// `R` is the root text component, and `S` is the text span component on children.
260
#[derive(SystemParam)]
261
pub struct TextWriter<'w, 's, R: TextRoot> {
262
// This is a resource because two TextWriters can't run in parallel.
263
scratch: ResMut<'w, TextIterScratch>,
264
roots: Query<
265
'w,
266
's,
267
(
268
&'static mut R,
269
&'static mut TextFont,
270
&'static mut TextColor,
271
&'static mut LineHeight,
272
),
273
Without<TextSpan>,
274
>,
275
spans: Query<
276
'w,
277
's,
278
(
279
&'static mut TextSpan,
280
&'static mut TextFont,
281
&'static mut TextColor,
282
&'static mut LineHeight,
283
),
284
Without<R>,
285
>,
286
children: Query<'w, 's, &'static Children>,
287
}
288
289
impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> {
290
/// Gets a mutable reference to a text span within a text block at a specific index in the flattened span list.
291
pub fn get(
292
&mut self,
293
root_entity: Entity,
294
index: usize,
295
) -> Option<(
296
Entity,
297
usize,
298
Mut<'_, String>,
299
Mut<'_, TextFont>,
300
Mut<'_, TextColor>,
301
Mut<'_, LineHeight>,
302
)> {
303
// Root
304
if index == 0 {
305
let (text, font, color, line_height) = self.roots.get_mut(root_entity).ok()?;
306
return Some((
307
root_entity,
308
0,
309
text.map_unchanged(|t| t.write_span()),
310
font,
311
color,
312
line_height,
313
));
314
}
315
316
// Prep stack.
317
let mut stack: Vec<(&Children, usize)> = self.scratch.take();
318
if let Ok(children) = self.children.get(root_entity) {
319
stack.push((children, 0));
320
}
321
322
// Span
323
let mut count = 1;
324
let (depth, entity) = 'l: loop {
325
let Some((children, idx)) = stack.last_mut() else {
326
self.scratch.recover(stack);
327
return None;
328
};
329
330
loop {
331
let Some(child) = children.get(*idx) else {
332
// All children at this stack entry have been iterated.
333
stack.pop();
334
break;
335
};
336
337
// Increment to prep the next entity in this stack level.
338
*idx += 1;
339
340
if !self.spans.contains(*child) {
341
continue;
342
};
343
count += 1;
344
345
if count - 1 == index {
346
let depth = stack.len();
347
self.scratch.recover(stack);
348
break 'l (depth, *child);
349
}
350
351
if let Ok(children) = self.children.get(*child) {
352
stack.push((children, 0));
353
break;
354
}
355
}
356
};
357
358
// Note: We do this outside the loop due to borrow checker limitations.
359
let (text, font, color, line_height) = self.spans.get_mut(entity).unwrap();
360
Some((
361
entity,
362
depth,
363
text.map_unchanged(|t| t.write_span()),
364
font,
365
color,
366
line_height,
367
))
368
}
369
370
/// Gets the text value of a text span within a text block at a specific index in the flattened span list.
371
pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option<Mut<'_, String>> {
372
self.get(root_entity, index).map(|(_, _, text, ..)| text)
373
}
374
375
/// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list.
376
pub fn get_font(&mut self, root_entity: Entity, index: usize) -> Option<Mut<'_, TextFont>> {
377
self.get(root_entity, index).map(|(_, _, _, font, ..)| font)
378
}
379
380
/// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list.
381
pub fn get_color(&mut self, root_entity: Entity, index: usize) -> Option<Mut<'_, TextColor>> {
382
self.get(root_entity, index)
383
.map(|(_, _, _, _, color, ..)| color)
384
}
385
386
/// Gets the [`LineHeight`] of a text span within a text block at a specific index in the flattened span list.
387
pub fn get_line_height(
388
&mut self,
389
root_entity: Entity,
390
index: usize,
391
) -> Option<Mut<'_, LineHeight>> {
392
self.get(root_entity, index)
393
.map(|(_, _, _, _, _, line_height)| line_height)
394
}
395
396
/// Gets the text value of a text span within a text block at a specific index in the flattened span list.
397
///
398
/// Panics if there is no span at the requested index.
399
pub fn text(&mut self, root_entity: Entity, index: usize) -> Mut<'_, String> {
400
self.get_text(root_entity, index).unwrap()
401
}
402
403
/// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list.
404
///
405
/// Panics if there is no span at the requested index.
406
pub fn font(&mut self, root_entity: Entity, index: usize) -> Mut<'_, TextFont> {
407
self.get_font(root_entity, index).unwrap()
408
}
409
410
/// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list.
411
///
412
/// Panics if there is no span at the requested index.
413
pub fn color(&mut self, root_entity: Entity, index: usize) -> Mut<'_, TextColor> {
414
self.get_color(root_entity, index).unwrap()
415
}
416
417
/// Gets the [`LineHeight`] of a text span within a text block at a specific index in the flattened span list.
418
///
419
/// Panics if there is no span at the requested index.
420
pub fn line_height(&mut self, root_entity: Entity, index: usize) -> Mut<'_, LineHeight> {
421
self.get_line_height(root_entity, index).unwrap()
422
}
423
424
/// Invokes a callback on each span in a text block, starting with the root entity.
425
pub fn for_each(
426
&mut self,
427
root_entity: Entity,
428
mut callback: impl FnMut(
429
Entity,
430
usize,
431
Mut<String>,
432
Mut<TextFont>,
433
Mut<TextColor>,
434
Mut<LineHeight>,
435
),
436
) {
437
self.for_each_until(root_entity, |a, b, c, d, e, f| {
438
(callback)(a, b, c, d, e, f);
439
true
440
});
441
}
442
443
/// Invokes a callback on each span's string value in a text block, starting with the root entity.
444
pub fn for_each_text(&mut self, root_entity: Entity, mut callback: impl FnMut(Mut<String>)) {
445
self.for_each(root_entity, |_, _, text, _, _, _| {
446
(callback)(text);
447
});
448
}
449
450
/// Invokes a callback on each span's [`TextFont`] in a text block, starting with the root entity.
451
pub fn for_each_font(&mut self, root_entity: Entity, mut callback: impl FnMut(Mut<TextFont>)) {
452
self.for_each(root_entity, |_, _, _, font, _, _| {
453
(callback)(font);
454
});
455
}
456
457
/// Invokes a callback on each span's [`TextColor`] in a text block, starting with the root entity.
458
pub fn for_each_color(
459
&mut self,
460
root_entity: Entity,
461
mut callback: impl FnMut(Mut<TextColor>),
462
) {
463
self.for_each(root_entity, |_, _, _, _, color, _| {
464
(callback)(color);
465
});
466
}
467
468
/// Invokes a callback on each span's [`LineHeight`] in a text block, starting with the root entity.
469
pub fn for_each_line_height(
470
&mut self,
471
root_entity: Entity,
472
mut callback: impl FnMut(Mut<LineHeight>),
473
) {
474
self.for_each(root_entity, |_, _, _, _, _, line_height| {
475
(callback)(line_height);
476
});
477
}
478
479
/// Invokes a callback on each span in a text block, starting with the root entity.
480
///
481
/// Traversal will stop when the callback returns `false`.
482
// TODO: find a way to consolidate get and for_each_until, or provide a real iterator. Lifetime issues are challenging here.
483
pub fn for_each_until(
484
&mut self,
485
root_entity: Entity,
486
mut callback: impl FnMut(
487
Entity,
488
usize,
489
Mut<String>,
490
Mut<TextFont>,
491
Mut<TextColor>,
492
Mut<LineHeight>,
493
) -> bool,
494
) {
495
// Root
496
let Ok((text, font, color, line_height)) = self.roots.get_mut(root_entity) else {
497
return;
498
};
499
if !(callback)(
500
root_entity,
501
0,
502
text.map_unchanged(|t| t.write_span()),
503
font,
504
color,
505
line_height,
506
) {
507
return;
508
}
509
510
// Prep stack.
511
let mut stack: Vec<(&Children, usize)> = self.scratch.take();
512
if let Ok(children) = self.children.get(root_entity) {
513
stack.push((children, 0));
514
}
515
516
// Span
517
loop {
518
let depth = stack.len();
519
let Some((children, idx)) = stack.last_mut() else {
520
self.scratch.recover(stack);
521
return;
522
};
523
524
loop {
525
let Some(child) = children.get(*idx) else {
526
// All children at this stack entry have been iterated.
527
stack.pop();
528
break;
529
};
530
531
// Increment to prep the next entity in this stack level.
532
*idx += 1;
533
534
let entity = *child;
535
let Ok((text, font, color, line_height)) = self.spans.get_mut(entity) else {
536
continue;
537
};
538
539
if !(callback)(
540
entity,
541
depth,
542
text.map_unchanged(|t| t.write_span()),
543
font,
544
color,
545
line_height,
546
) {
547
self.scratch.recover(stack);
548
return;
549
}
550
551
if let Ok(children) = self.children.get(entity) {
552
stack.push((children, 0));
553
break;
554
}
555
}
556
}
557
}
558
}
559
560