Skip to content

Commit c2c19e5

Browse files
UkoeHBickshonpecart
authored
Text rework (#15591)
**Ready for review. Examples migration progress: 100%.** # Objective - Implement #15014 ## Solution This implements [cart's proposal](#15014 (comment)) faithfully except for one change. I separated `TextSpan` from `TextSpan2d` because `TextSpan` needs to require the `GhostNode` component, which is a `bevy_ui` component only usable by UI. Extra changes: - Added `EntityCommands::commands_mut` that returns a mutable reference. This is a blocker for extension methods that return something other than `self`. Note that `sickle_ui`'s `UiBuilder::commands` returns a mutable reference for this reason. ## Testing - [x] Text examples all work. --- ## Showcase TODO: showcase-worthy ## Migration Guide TODO: very breaking ### Accessing text spans by index Text sections are now text sections on different entities in a hierarchy, Use the new `TextReader` and `TextWriter` system parameters to access spans by index. Before: ```rust fn refresh_text(mut query: Query<&mut Text, With<TimeText>>, time: Res<Time>) { let text = query.single_mut(); text.sections[1].value = format_time(time.elapsed()); } ``` After: ```rust fn refresh_text( query: Query<Entity, With<TimeText>>, mut writer: UiTextWriter, time: Res<Time> ) { let entity = query.single(); *writer.text(entity, 1) = format_time(time.elapsed()); } ``` ### Iterating text spans Text spans are now entities in a hierarchy, so the new `UiTextReader` and `UiTextWriter` system parameters provide ways to iterate that hierarchy. The `UiTextReader::iter` method will give you a normal iterator over spans, and `UiTextWriter::for_each` lets you visit each of the spans. --------- Co-authored-by: ickshonpe <[email protected]> Co-authored-by: Carter Anderson <[email protected]>
1 parent 0b2e0cf commit c2c19e5

File tree

146 files changed

+3098
-2708
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+3098
-2708
lines changed

crates/bevy_dev_tools/src/fps_overlay.rs

+24-17
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
77
use bevy_ecs::{
88
change_detection::DetectChangesMut,
99
component::Component,
10+
entity::Entity,
1011
query::With,
1112
schedule::{common_conditions::resource_changed, IntoSystemConfigs},
1213
system::{Commands, Query, Res, Resource},
1314
};
1415
use bevy_hierarchy::{BuildChildren, ChildBuild};
1516
use bevy_render::view::Visibility;
16-
use bevy_text::{Font, Text, TextSection, TextStyle};
17+
use bevy_text::{Font, TextSpan, TextStyle};
1718
use bevy_ui::{
18-
node_bundles::{NodeBundle, TextBundle},
19+
node_bundles::NodeBundle,
20+
widget::{Text, UiTextWriter},
1921
GlobalZIndex, PositionType, Style,
2022
};
2123
use bevy_utils::default;
@@ -72,6 +74,7 @@ impl Default for FpsOverlayConfig {
7274
font: Handle::<Font>::default(),
7375
font_size: 32.0,
7476
color: Color::WHITE,
77+
..default()
7578
},
7679
enabled: true,
7780
}
@@ -95,35 +98,39 @@ fn setup(mut commands: Commands, overlay_config: Res<FpsOverlayConfig>) {
9598
},
9699
GlobalZIndex(FPS_OVERLAY_ZINDEX),
97100
))
98-
.with_children(|c| {
99-
c.spawn((
100-
TextBundle::from_sections([
101-
TextSection::new("FPS: ", overlay_config.text_config.clone()),
102-
TextSection::from_style(overlay_config.text_config.clone()),
103-
]),
101+
.with_children(|p| {
102+
p.spawn((
103+
Text::new("FPS: "),
104+
overlay_config.text_config.clone(),
104105
FpsText,
105-
));
106+
))
107+
.with_child((TextSpan::default(), overlay_config.text_config.clone()));
106108
});
107109
}
108110

109-
fn update_text(diagnostic: Res<DiagnosticsStore>, mut query: Query<&mut Text, With<FpsText>>) {
110-
for mut text in &mut query {
111+
fn update_text(
112+
diagnostic: Res<DiagnosticsStore>,
113+
query: Query<Entity, With<FpsText>>,
114+
mut writer: UiTextWriter,
115+
) {
116+
for entity in &query {
111117
if let Some(fps) = diagnostic.get(&FrameTimeDiagnosticsPlugin::FPS) {
112118
if let Some(value) = fps.smoothed() {
113-
text.sections[1].value = format!("{value:.2}");
119+
*writer.text(entity, 1) = format!("{value:.2}");
114120
}
115121
}
116122
}
117123
}
118124

119125
fn customize_text(
120126
overlay_config: Res<FpsOverlayConfig>,
121-
mut query: Query<&mut Text, With<FpsText>>,
127+
query: Query<Entity, With<FpsText>>,
128+
mut writer: UiTextWriter,
122129
) {
123-
for mut text in &mut query {
124-
for section in text.sections.iter_mut() {
125-
section.style = overlay_config.text_config.clone();
126-
}
130+
for entity in &query {
131+
writer.for_each_style(entity, |mut style| {
132+
*style = overlay_config.text_config.clone();
133+
});
127134
}
128135
}
129136

crates/bevy_ecs/src/system/commands/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,7 @@ pub struct EntityCommands<'a> {
939939
pub(crate) commands: Commands<'a, 'a>,
940940
}
941941

942-
impl EntityCommands<'_> {
942+
impl<'a> EntityCommands<'a> {
943943
/// Returns the [`Entity`] id of the entity.
944944
///
945945
/// # Example
@@ -1533,6 +1533,11 @@ impl EntityCommands<'_> {
15331533
self.commands.reborrow()
15341534
}
15351535

1536+
/// Returns a mutable reference to the underlying [`Commands`].
1537+
pub fn commands_mut(&mut self) -> &mut Commands<'a, 'a> {
1538+
&mut self.commands
1539+
}
1540+
15361541
/// Sends a [`Trigger`] targeting this entity. This will run any [`Observer`] of the `event` that
15371542
/// watches this entity.
15381543
///

crates/bevy_text/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" }
1818
bevy_color = { path = "../bevy_color", version = "0.15.0-dev" }
1919
bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" }
2020
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
21+
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" }
2122
bevy_math = { path = "../bevy_math", version = "0.15.0-dev" }
2223
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
2324
"bevy",
@@ -36,6 +37,7 @@ derive_more = { version = "1", default-features = false, features = [
3637
"display",
3738
] }
3839
serde = { version = "1", features = ["derive"] }
40+
smallvec = "1.13"
3941
unicode-bidi = "0.3.13"
4042
sys-locale = "0.3.0"
4143

crates/bevy_text/src/font_atlas_set.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ pub struct FontAtlasKey(pub u32, pub FontSmoothing);
6060
/// A `FontAtlasSet` is an [`Asset`].
6161
///
6262
/// There is one `FontAtlasSet` for each font:
63-
/// - When a [`Font`] is loaded as an asset and then used in [`Text`](crate::Text),
63+
/// - When a [`Font`] is loaded as an asset and then used in [`TextStyle`](crate::TextStyle),
6464
/// a `FontAtlasSet` asset is created from a weak handle to the `Font`.
65-
/// - ~When a font is loaded as a system font, and then used in [`Text`](crate::Text),
65+
/// - ~When a font is loaded as a system font, and then used in [`TextStyle`](crate::TextStyle),
6666
/// a `FontAtlasSet` asset is created and stored with a strong handle to the `FontAtlasSet`.~
6767
/// (*Note that system fonts are not currently supported by the `TextPipeline`.*)
6868
///

crates/bevy_text/src/glyph.rs

+5-10
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ use bevy_sprite::TextureAtlasLayout;
1313
/// Used in [`TextPipeline::queue_text`](crate::TextPipeline::queue_text) and [`crate::TextLayoutInfo`] for rendering glyphs.
1414
#[derive(Debug, Clone, Reflect)]
1515
pub struct PositionedGlyph {
16-
/// The position of the glyph in the [`Text`](crate::Text)'s bounding box.
16+
/// The position of the glyph in the text block's bounding box.
1717
pub position: Vec2,
1818
/// The width and height of the glyph in logical pixels.
1919
pub size: Vec2,
2020
/// Information about the glyph's atlas.
2121
pub atlas_info: GlyphAtlasInfo,
22-
/// The index of the glyph in the [`Text`](crate::Text)'s sections.
23-
pub section_index: usize,
22+
/// The index of the glyph in the [`ComputedTextBlock`](crate::ComputedTextBlock)'s tracked spans.
23+
pub span_index: usize,
2424
/// TODO: In order to do text editing, we need access to the size of glyphs and their index in the associated String.
2525
/// For example, to figure out where to place the cursor in an input box from the mouse's position.
2626
/// Without this, it's only possible in texts where each glyph is one byte. Cosmic text has methods for this
@@ -30,17 +30,12 @@ pub struct PositionedGlyph {
3030

3131
impl PositionedGlyph {
3232
/// Creates a new [`PositionedGlyph`]
33-
pub fn new(
34-
position: Vec2,
35-
size: Vec2,
36-
atlas_info: GlyphAtlasInfo,
37-
section_index: usize,
38-
) -> Self {
33+
pub fn new(position: Vec2, size: Vec2, atlas_info: GlyphAtlasInfo, span_index: usize) -> Self {
3934
Self {
4035
position,
4136
size,
4237
atlas_info,
43-
section_index,
38+
span_index,
4439
byte_index: 0,
4540
}
4641
}

crates/bevy_text/src/lib.rs

+21-14
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//!
1515
//! The [`TextPipeline`] resource does all of the heavy lifting for rendering text.
1616
//!
17-
//! [`Text`] is first measured by creating a [`TextMeasureInfo`] in [`TextPipeline::create_text_measure`],
17+
//! UI `Text` is first measured by creating a [`TextMeasureInfo`] in [`TextPipeline::create_text_measure`],
1818
//! which is called by the `measure_text_system` system of `bevy_ui`.
1919
//!
2020
//! Note that text measurement is only relevant in a UI context.
@@ -23,7 +23,7 @@
2323
//! or [`text2d::update_text2d_layout`] system (in a 2d world space context)
2424
//! passes it into [`TextPipeline::queue_text`], which:
2525
//!
26-
//! 1. creates a [`Buffer`](cosmic_text::Buffer) from the [`TextSection`]s, generating new [`FontAtlasSet`]s if necessary.
26+
//! 1. updates a [`Buffer`](cosmic_text::Buffer) from the [`TextSpan`]s, generating new [`FontAtlasSet`]s if necessary.
2727
//! 2. iterates over each glyph in the [`Buffer`](cosmic_text::Buffer) to create a [`PositionedGlyph`],
2828
//! retrieving glyphs from the cache, or rasterizing to a [`FontAtlas`] if necessary.
2929
//! 3. [`PositionedGlyph`]s are stored in a [`TextLayoutInfo`],
@@ -43,6 +43,7 @@ mod glyph;
4343
mod pipeline;
4444
mod text;
4545
mod text2d;
46+
mod text_access;
4647

4748
pub use cosmic_text;
4849

@@ -56,13 +57,17 @@ pub use glyph::*;
5657
pub use pipeline::*;
5758
pub use text::*;
5859
pub use text2d::*;
60+
pub use text_access::*;
5961

6062
/// The text prelude.
6163
///
6264
/// This includes the most common types in this crate, re-exported for your convenience.
6365
pub mod prelude {
6466
#[doc(hidden)]
65-
pub use crate::{Font, JustifyText, Text, Text2dBundle, TextError, TextSection, TextStyle};
67+
pub use crate::{
68+
Font, JustifyText, LineBreak, Text2d, TextBlock, TextError, TextReader2d, TextSpan,
69+
TextStyle, TextWriter2d,
70+
};
6671
}
6772

6873
use bevy_app::prelude::*;
@@ -87,7 +92,7 @@ pub const DEFAULT_FONT_DATA: &[u8] = include_bytes!("FiraMono-subset.ttf");
8792
pub struct TextPlugin;
8893

8994
/// Text is rendered for two different view projections;
90-
/// 2-dimensional text ([`Text2dBundle`]) is rendered in "world space" with a `BottomToTop` Y-axis,
95+
/// 2-dimensional text ([`Text2d`]) is rendered in "world space" with a `BottomToTop` Y-axis,
9196
/// while UI is rendered with a `TopToBottom` Y-axis.
9297
/// This matters for text because the glyph positioning is different in either layout.
9398
/// For `TopToBottom`, 0 is the top of the text, while for `BottomToTop` 0 is the bottom.
@@ -98,35 +103,37 @@ pub enum YAxisOrientation {
98103
BottomToTop,
99104
}
100105

101-
/// A convenient alias for `With<Text>`, for use with
102-
/// [`bevy_render::view::VisibleEntities`].
103-
pub type WithText = With<Text>;
106+
/// System set in [`PostUpdate`] where all 2d text update systems are executed.
107+
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
108+
pub struct Update2dText;
104109

105110
impl Plugin for TextPlugin {
106111
fn build(&self, app: &mut App) {
107112
app.init_asset::<Font>()
108-
.register_type::<Text>()
113+
.register_type::<Text2d>()
114+
.register_type::<TextSpan>()
109115
.register_type::<TextBounds>()
110116
.init_asset_loader::<FontLoader>()
111117
.init_resource::<FontAtlasSets>()
112118
.init_resource::<TextPipeline>()
113119
.init_resource::<CosmicFontSystem>()
114120
.init_resource::<SwashCache>()
121+
.init_resource::<TextIterScratch>()
115122
.add_systems(
116123
PostUpdate,
117124
(
118-
calculate_bounds_text2d
119-
.in_set(VisibilitySystems::CalculateBounds)
120-
.after(update_text2d_layout),
125+
remove_dropped_font_atlas_sets,
126+
detect_text_needs_rerender::<Text2d>,
121127
update_text2d_layout
122-
.after(remove_dropped_font_atlas_sets)
123128
// Potential conflict: `Assets<Image>`
124129
// In practice, they run independently since `bevy_render::camera_update_system`
125130
// will only ever observe its own render target, and `update_text2d_layout`
126131
// will never modify a pre-existing `Image` asset.
127132
.ambiguous_with(CameraUpdateSystem),
128-
remove_dropped_font_atlas_sets,
129-
),
133+
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
134+
)
135+
.chain()
136+
.in_set(Update2dText),
130137
)
131138
.add_systems(Last, trim_cosmic_cache);
132139

0 commit comments

Comments
 (0)