This commit is contained in:
ickshonpe 2025-07-17 15:12:30 +02:00 committed by GitHub
commit 4f50daf682
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 66 additions and 48 deletions

View File

@ -117,19 +117,25 @@ impl Plugin for TextPlugin {
.add_systems(
PostUpdate,
(
remove_dropped_font_atlas_sets.before(AssetEventSystems),
detect_text_needs_rerender::<Text2d>,
update_text2d_layout
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
// will only ever observe its own render target, and `update_text2d_layout`
// will never modify a pre-existing `Image` asset.
.ambiguous_with(CameraUpdateSystems),
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
)
.chain()
.in_set(Text2dUpdateSystems)
.after(AnimationSystems),
(
remove_dropped_font_atlas_sets.before(AssetEventSystems),
detect_text_root_needs_rerender::<Text2d>,
update_text2d_layout
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
// will only ever observe its own render target, and `update_text2d_layout`
// will never modify a pre-existing `Image` asset.
.ambiguous_with(CameraUpdateSystems),
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
)
.chain()
.in_set(Text2dUpdateSystems)
.after(AnimationSystems),
detect_text_span_needs_rerender
.before(Text2dUpdateSystems)
.ambiguous_with(CameraUpdateSystems)
.after(AnimationSystems),
),
)
.add_systems(Last, trim_cosmic_cache);

View File

@ -76,7 +76,7 @@ impl ComputedTextBlock {
/// Indicates if the text needs to be refreshed in [`TextLayoutInfo`].
///
/// Updated automatically by [`detect_text_needs_rerender`] and cleared
/// Updated automatically by [`detect_text_root_needs_rerender`] and [`detect_text_span_needs_rerender`], and cleared
/// by [`TextPipeline`](crate::TextPipeline) methods.
pub fn needs_rerender(&self) -> bool {
self.needs_rerender
@ -472,11 +472,10 @@ pub enum FontSmoothing {
/// System that detects changes to text blocks and sets `ComputedTextBlock::should_rerender`.
///
/// Generic over the root text component and text span component. For example, [`Text2d`](crate::Text2d)/[`TextSpan`] for
/// 2d or `Text`/[`TextSpan`] for UI.
pub fn detect_text_needs_rerender<Root: Component>(
changed_roots: Query<
Entity,
/// Generic over the root text component.
pub fn detect_text_root_needs_rerender<Root: Component>(
mut changed_roots: Query<
&mut ComputedTextBlock,
(
Or<(
Changed<Root>,
@ -489,6 +488,30 @@ pub fn detect_text_needs_rerender<Root: Component>(
With<TextLayout>,
),
>,
roots_without_computed_block: Query<Entity, (With<Root>, Without<ComputedTextBlock>)>,
) {
// Root entity:
// - Root component changed.
// - TextFont on root changed.
// - TextLayout changed.
// - Root children changed (can include additions and removals).
for mut computed in changed_roots.iter_mut() {
computed.needs_rerender = true;
}
for entity in roots_without_computed_block.iter() {
// If the root entity does not have a ComputedTextBlock, then it needs one.
// This can happen if the root was spawned without a ComputedTextBlock, or if it was removed.
once!(warn!(
"found entity {} with a root text component ({}) that has no ComputedTextBlock; this warning only prints once",
entity,
core::any::type_name::<Root>()
));
}
}
/// System that detects changes to text spans and sets `ComputedTextBlock::should_rerender`.
pub fn detect_text_span_needs_rerender(
changed_spans: Query<
(Entity, Option<&ChildOf>, Has<TextLayout>),
(
@ -509,20 +532,6 @@ pub fn detect_text_needs_rerender<Root: Component>(
Has<TextSpan>,
)>,
) {
// Root entity:
// - Root component changed.
// - TextFont on root changed.
// - TextLayout changed.
// - Root children changed (can include additions and removals).
for root in changed_roots.iter() {
let Ok((_, Some(mut computed), _)) = computed.get_mut(root) else {
once!(warn!("found entity {} with a root text component ({}) but no ComputedTextBlock; this warning only \
prints once", root, core::any::type_name::<Root>()));
continue;
};
computed.needs_rerender = true;
}
// Span entity:
// - Span component changed.
// - Span TextFont changed.
@ -530,16 +539,15 @@ pub fn detect_text_needs_rerender<Root: Component>(
for (entity, maybe_span_child_of, has_text_block) in changed_spans.iter() {
if has_text_block {
once!(warn!("found entity {} with a TextSpan that has a TextLayout, which should only be on root \
text entities (that have {}); this warning only prints once",
entity, core::any::type_name::<Root>()));
text entities; this warning only prints once",
entity));
}
let Some(span_child_of) = maybe_span_child_of else {
once!(warn!(
"found entity {} with a TextSpan that has no parent; it should have an ancestor \
with a root text component ({}); this warning only prints once",
entity,
core::any::type_name::<Root>()
with a root text component; this warning only prints once",
entity
));
continue;
};
@ -568,9 +576,8 @@ pub fn detect_text_needs_rerender<Root: Component>(
let Some(next_child_of) = maybe_child_of else {
once!(warn!(
"found entity {} with a TextSpan that has no ancestor with the root text \
component ({}); this warning only prints once",
component; this warning only prints once",
entity,
core::any::type_name::<Root>()
));
break;
};

View File

@ -389,7 +389,9 @@ mod tests {
use bevy_asset::{load_internal_binary_asset, Handle};
use bevy_ecs::schedule::IntoScheduleConfigs;
use crate::{detect_text_needs_rerender, TextIterScratch};
use crate::{
detect_text_root_needs_rerender, detect_text_span_needs_rerender, TextIterScratch,
};
use super::*;
@ -409,7 +411,8 @@ mod tests {
.add_systems(
Update,
(
detect_text_needs_rerender::<Text2d>,
detect_text_root_needs_rerender::<Text2d>,
detect_text_span_needs_rerender,
update_text2d_layout,
calculate_bounds_text2d,
)

View File

@ -34,6 +34,7 @@ mod layout;
mod stack;
mod ui_node;
use bevy_text::detect_text_span_needs_rerender;
pub use focus::*;
pub use geometry::*;
pub use gradients::*;
@ -201,7 +202,7 @@ impl Plugin for UiPlugin {
let ui_layout_system_config = ui_layout_system_config
// Text and Text2D operate on disjoint sets of entities
.ambiguous_with(bevy_text::update_text2d_layout)
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>);
.ambiguous_with(bevy_text::detect_text_root_needs_rerender::<bevy_text::Text2d>);
app.add_systems(
PostUpdate,
@ -252,26 +253,27 @@ fn build_text_interop(app: &mut App) {
PostUpdate,
(
(
bevy_text::detect_text_needs_rerender::<Text>,
bevy_text::detect_text_root_needs_rerender::<Text>,
widget::measure_text_system,
)
.chain()
.in_set(UiSystems::Content)
// Text and Text2d are independent.
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>)
.ambiguous_with(bevy_text::detect_text_root_needs_rerender::<bevy_text::Text2d>)
// Potential conflict: `Assets<Image>`
// Since both systems will only ever insert new [`Image`] assets,
// they will never observe each other's effects.
.ambiguous_with(bevy_text::update_text2d_layout)
// We assume Text is on disjoint UI entities to ImageNode and UiTextureAtlasImage
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(widget::update_image_content_size_system),
.ambiguous_with(widget::update_image_content_size_system)
.after(detect_text_span_needs_rerender),
widget::text_system
.in_set(UiSystems::PostLayout)
.after(bevy_text::remove_dropped_font_atlas_sets)
.before(bevy_asset::AssetEventSystems)
// Text2d and bevy_ui text are entirely on separate entities
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>)
.ambiguous_with(bevy_text::detect_text_root_needs_rerender::<bevy_text::Text2d>)
.ambiguous_with(bevy_text::update_text2d_layout)
.ambiguous_with(bevy_text::calculate_bounds_text2d),
),

View File

@ -111,7 +111,7 @@ fn main() {
}
if args.recompute_text {
app.add_systems(Update, |mut text_query: Query<&mut Text>| {
app.add_systems(Update, |mut text_query: Query<&mut TextSpan>| {
text_query
.iter_mut()
.for_each(|mut text| text.set_changed());