Skip to content

Commit 99b9678

Browse files
committed
TextReader/TextWriter WIP
1 parent cd6dc92 commit 99b9678

File tree

7 files changed

+408
-283
lines changed

7 files changed

+408
-283
lines changed

crates/bevy_text/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ mod glyph;
4343
mod pipeline;
4444
mod text;
4545
mod text2d;
46-
mod text_blocks;
46+
mod text_access;
4747

4848
pub use cosmic_text;
4949

@@ -57,7 +57,7 @@ pub use glyph::*;
5757
pub use pipeline::*;
5858
pub use text::*;
5959
pub use text2d::*;
60-
pub use text_blocks::*;
60+
pub use text_access::*;
6161

6262
/// The text prelude.
6363
///
@@ -117,7 +117,7 @@ impl Plugin for TextPlugin {
117117
.init_resource::<TextPipeline>()
118118
.init_resource::<CosmicFontSystem>()
119119
.init_resource::<SwashCache>()
120-
.init_resource::<TextSpansScratch>()
120+
.init_resource::<TextIterScratch>()
121121
.add_systems(
122122
PostUpdate,
123123
(

crates/bevy_text/src/text2d.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::pipeline::CosmicFontSystem;
22
use crate::{
33
ComputedTextBlock, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, TextBlock,
4-
TextBlocks, TextBounds, TextError, TextLayoutInfo, TextPipeline, TextSpanReader, TextStyle,
4+
TextBlocks, TextBounds, TextError, TextLayoutInfo, TextPipeline, TextSpanAccess, TextStyle,
55
YAxisOrientation,
66
};
77
use bevy_asset::Assets;
@@ -94,10 +94,13 @@ impl Text2d {
9494
}
9595
}
9696

97-
impl TextSpanReader for Text2d {
97+
impl TextSpanAccess for Text2d {
9898
fn read_span(&self) -> &str {
9999
self.as_str()
100100
}
101+
fn write_span(&mut self) -> &mut String {
102+
&mut *self
103+
}
101104
}
102105

103106
impl From<&str> for Text2d {
@@ -150,10 +153,13 @@ world.spawn((
150153
#[require(TextStyle, Visibility(visibility_hidden), Transform)]
151154
pub struct TextSpan2d(pub String);
152155

153-
impl TextSpanReader for TextSpan2d {
156+
impl TextSpanAccess for TextSpan2d {
154157
fn read_span(&self) -> &str {
155158
self.as_str()
156159
}
160+
fn write_span(&mut self) -> &mut String {
161+
&mut *self
162+
}
157163
}
158164

159165
fn visibility_hidden() -> Visibility {
@@ -382,7 +388,7 @@ mod tests {
382388
use bevy_asset::{load_internal_binary_asset, Handle};
383389
use bevy_ecs::{event::Events, schedule::IntoSystemConfigs};
384390

385-
use crate::{detect_text_needs_rerender, TextSpansScratch};
391+
use crate::{detect_text_needs_rerender, TextIterScratch};
386392

387393
use super::*;
388394

@@ -399,7 +405,7 @@ mod tests {
399405
.init_resource::<TextPipeline>()
400406
.init_resource::<CosmicFontSystem>()
401407
.init_resource::<SwashCache>()
402-
.init_resource::<TextSpansScratch>()
408+
.init_resource::<TextIterScratch>()
403409
.add_systems(
404410
Update,
405411
(

crates/bevy_text/src/text_access.rs

+259
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
use bevy_ecs::{prelude::*, system::{Query, SystemParam}};
2+
use bevy_hierarchy::Children;
3+
4+
use crate::TextStyle;
5+
6+
/// Helper trait for using the [`TextReader`] system param.
7+
pub trait TextSpanAccess: Component {
8+
/// Gets the text span's string.
9+
fn read_span(&self) -> &str;
10+
/// Gets mutable reference to the text span's string.
11+
fn write_span(&mut self) -> &mut String;
12+
}
13+
14+
#[derive(Resource, Default)]
15+
pub(crate) struct TextIterScratch {
16+
stack: Vec<(&'static Children, usize)>,
17+
}
18+
19+
/// System parameter for reading text spans in a [`TextBlock`].
20+
///
21+
/// `R` is the root text component, and `S` is the text span component on children.
22+
#[derive(SystemParam)]
23+
pub struct TextReader<'w, 's, R: TextSpanAccess, S: TextSpanAccess> {
24+
scratch: ResMut<'w, TextIterScratch>,
25+
roots: Query<'w, 's, (&'static R, &'static TextStyle, Option<&'static Children>)>,
26+
spans: Query<
27+
'w,
28+
's,
29+
(
30+
Entity,
31+
&'static S,
32+
&'static TextStyle,
33+
Option<&'static Children>,
34+
),
35+
>,
36+
}
37+
38+
impl<'w, 's, R: TextSpanAccess, S: TextSpanAccess> TextReader<'w, 's, R, S> {
39+
/// Returns an iterator over text spans in a text block, starting with the root entity.
40+
pub fn iter<'a>(&'a mut self, root_entity: Entity) -> TextSpanIter<'a, R, S> {
41+
let stack = core::mem::take(&mut self.scratch.stack)
42+
.into_iter()
43+
.map(|_| -> (&Children, usize) { unreachable!() })
44+
.collect();
45+
46+
TextSpanIter {
47+
scratch: &mut self.scratch,
48+
root_entity: Some(root_entity),
49+
stack,
50+
roots: &self.roots,
51+
spans: &self.spans,
52+
}
53+
}
54+
55+
/// Gets a text span within a text block at a specific index in the flattened span list.
56+
pub fn get_by_index<'a>(&'a mut self, root_entity: Entity, index: usize) -> Option<(&'a str, &'a TextStyle)> {
57+
self.iter(root_entity).nth(index).map(|(_, _, text, style)| (text, style))
58+
}
59+
}
60+
61+
/// System parameter for reading and writing text spans in a [`TextBlock`].
62+
///
63+
/// `R` is the root text component, and `S` is the text span component on children.
64+
#[derive(SystemParam)]
65+
pub struct TextWriter<'w, 's, R: TextSpanAccess, S: TextSpanAccess> {
66+
scratch: ResMut<'w, TextIterScratch>,
67+
roots: Query<'w, 's, (&'static mut R, &'static mut TextStyle), Without<S>>,
68+
spans: Query<
69+
'w,
70+
's,
71+
(
72+
Entity,
73+
&'static mut S,
74+
&'static mut TextStyle,
75+
),
76+
Without<R>
77+
>,
78+
children: Query<'w, 's, &'static Children>,
79+
}
80+
81+
impl<'w, 's, R: TextSpanAccess, S: TextSpanAccess> TextWriter<'w, 's, R, S> {
82+
/// Returns a mutatable iterator over text spans in a text block, starting with the root entity.
83+
pub fn iter<'a>(&'a mut self, root_entity: Entity) -> TextSpanIterMut<'a, R, S> {
84+
let stack = core::mem::take(&mut self.scratch.stack)
85+
.into_iter()
86+
.map(|_| -> (&Children, usize) { unreachable!() })
87+
.collect();
88+
89+
TextSpanIterMut {
90+
scratch: &mut self.scratch,
91+
root_entity: Some(root_entity),
92+
stack,
93+
roots: &mut self.roots,
94+
spans: &mut self.spans,
95+
children: &self.children,
96+
}
97+
}
98+
99+
/// Gets a mutable reference to a text span within a text block at a specific index in the flattened span list.
100+
pub fn get_by_index<'a>(&'a mut self, root_entity: Entity, index: usize) -> Option<(Mut<'a, String>, Mut<'a, TextStyle>)> {
101+
self.iter(root_entity).nth(index).map(|(_, _, text, style)| (text, style))
102+
}
103+
}
104+
105+
/// Iterator returned by [`TextReader::iter`] and [`TextWriter::iter`].
106+
///
107+
/// Iterates all spans in a text block according to hierarchy traversal order.
108+
/// Does *not* flatten interspersed ghost nodes. Only contiguous spans are traversed.
109+
// TODO: Use this iterator design in UiChildrenIter to reduce allocations.
110+
pub struct TextSpanIter<'a, R: TextSpanAccess, S: TextSpanAccess> {
111+
scratch: &'a mut TextIterScratch,
112+
root_entity: Option<Entity>,
113+
/// Stack of (children, next index into children).
114+
stack: Vec<(&'a Children, usize)>,
115+
roots: &'a Query<'a, 'a, (&'static R, &'static TextStyle, Option<&'static Children>)>,
116+
spans: &'a Query<
117+
'a,
118+
'a,
119+
(
120+
Entity,
121+
&'static S,
122+
&'static TextStyle,
123+
Option<&'static Children>,
124+
),
125+
>,
126+
}
127+
128+
impl<'a, R: TextSpanAccess, S: TextSpanAccess> Iterator for TextSpanIter<'a, R, S> {
129+
/// Item = (entity in text block, hierarchy depth in the block, span text, span style).
130+
type Item = (Entity, usize, &'a str, &'a TextStyle);
131+
fn next(&mut self) -> Option<Self::Item> {
132+
// Root
133+
if let Some(root_entity) = self.root_entity.take() {
134+
if let Ok((text, style, maybe_children)) = self.roots.get(root_entity) {
135+
if let Some(children) = maybe_children {
136+
self.stack.push((children, 0));
137+
}
138+
return Some((root_entity, 0, text.read_span(), style));
139+
} else {
140+
return None;
141+
}
142+
}
143+
144+
// Span
145+
loop {
146+
let Some((children, idx)) = self.stack.last_mut() else {
147+
return None;
148+
};
149+
150+
loop {
151+
let Some(child) = children.get(*idx) else {
152+
break;
153+
};
154+
155+
// Increment to prep the next entity in this stack level.
156+
*idx += 1;
157+
158+
let Ok((entity, span, style, maybe_children)) = self.spans.get(*child) else {
159+
continue;
160+
};
161+
162+
let depth = self.stack.len();
163+
if let Some(children) = maybe_children {
164+
self.stack.push((children, 0));
165+
}
166+
return Some((entity, depth, span.read_span(), style));
167+
}
168+
169+
// All children at this stack entry have been iterated.
170+
self.stack.pop();
171+
}
172+
}
173+
}
174+
175+
impl<'a, R: TextSpanAccess, S: TextSpanAccess> Drop for TextSpanIter<'a, R, S> {
176+
fn drop(&mut self) {
177+
// Return the internal stack.
178+
let mut stack = std::mem::take(&mut self.stack);
179+
stack.clear();
180+
self.scratch.stack = stack
181+
.into_iter()
182+
.map(|_| -> (&'static Children, usize) { unreachable!() })
183+
.collect();
184+
}
185+
}
186+
187+
/// Iterator returned by [`TextWriter::iter_mut`].
188+
///
189+
/// Iterates all spans in a text block according to hierarchy traversal order.
190+
/// Does *not* flatten interspersed ghost nodes. Only contiguous spans are traversed.
191+
// TODO: Use this iterator design in UiChildrenIter to reduce allocations.
192+
pub struct TextSpanIterMut<'a, R: TextSpanAccess, S: TextSpanAccess> {
193+
scratch: &'a mut TextIterScratch,
194+
root_entity: Option<Entity>,
195+
/// Stack of (children, next index into children).
196+
stack: Vec<(&'a Children, usize)>,
197+
roots: &'a mut Query<'a, 'a, (&'a mut R, &'a mut TextStyle), Without<S>>,
198+
spans: &'a mut Query<'a,'a, (Entity, &'a mut S, &'a mut TextStyle), Without<R>>,
199+
children: &'a Query<'a, 'a, &'a Children>,
200+
}
201+
202+
impl<'a, R: TextSpanAccess, S: TextSpanAccess> Iterator for TextSpanIterMut<'a, R, S> {
203+
/// Item = (entity in text block, hierarchy depth in the block, span text, span style).
204+
type Item = (Entity, usize, Mut<'a, String>, Mut<'a, TextStyle>);
205+
fn next(&mut self) -> Option<Self::Item> {
206+
// Root
207+
if let Some(root_entity) = self.root_entity.take() {
208+
if let Ok((text, style)) = self.roots.get_mut(root_entity) {
209+
if let Ok(children) = self.children.get(root_entity) {
210+
self.stack.push((children, 0));
211+
}
212+
return Some((root_entity, 0, text.map_unchanged(|t| t.write_span()), style));
213+
} else {
214+
return None;
215+
}
216+
}
217+
218+
// Span
219+
loop {
220+
let Some((children, idx)) = self.stack.last_mut() else {
221+
return None;
222+
};
223+
224+
loop {
225+
let Some(child) = children.get(*idx) else {
226+
break;
227+
};
228+
229+
// Increment to prep the next entity in this stack level.
230+
*idx += 1;
231+
232+
let Ok((entity, span, style)) = self.spans.get_mut(*child) else {
233+
continue;
234+
};
235+
236+
let depth = self.stack.len();
237+
if let Ok(children) = self.children.get(entity) {
238+
self.stack.push((children, 0));
239+
}
240+
return Some((entity, depth, span.map_unchanged(|t| t.write_span()), style));
241+
}
242+
243+
// All children at this stack entry have been iterated.
244+
self.stack.pop();
245+
}
246+
}
247+
}
248+
249+
impl<'a, R: TextSpanAccess, S: TextSpanAccess> Drop for TextSpanIterMut<'a, R, S> {
250+
fn drop(&mut self) {
251+
// Return the internal stack.
252+
let mut stack = std::mem::take(&mut self.stack);
253+
stack.clear();
254+
self.scratch.stack = stack
255+
.into_iter()
256+
.map(|_| -> (&'static Children, usize) { unreachable!() })
257+
.collect();
258+
}
259+
}

0 commit comments

Comments
 (0)