From 1fdb7c1b9031a397637be82e237fdc83711e6000 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 18 Sep 2023 09:54:39 +0100 Subject: [PATCH] `ContentSize` replacement fix (#9753) # Objective If you remove a `ContentSize` component from a Bevy UI entity and then replace it `ui_layout_system` will remove the measure func from the internal Taffy layout tree but no new measure func will be generated to replace it since it's the widget systems that are responsible for creating their respective measure funcs not `ui_layout_system`. The widget systems only perform a measure func update on changes to a widget entity's content. This means that until its content is changed in some way, no content will be displayed by the node. ### Example This example spawns a text node which disappears after a few moments once its `ContentSize` component is replaced. ```rust use bevy::prelude::*; use bevy::ui::ContentSize; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, delayed_replacement) .run(); } fn setup(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); commands.spawn( TextBundle::from_section( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", TextStyle::default(), ) ); } // Waits a few frames to make sure the font is loaded and the text's glyph layout has been generated. fn delayed_replacement(mut commands: Commands, mut count: Local, query: Query>) { *count += 1; if *count == 10 { for item in query.iter() { commands .entity(item) .remove::() .insert(ContentSize::default()); } } } ``` ## Solution Perform `ui_layout_system`'s `ContentSize` removal detection and resolution first, before the measure func updates. Then in the widget systems, generate a new `Measure` when a `ContentSize` component is added to a widget entity. ## Changelog * `measure_text_system`, `update_image_content_size_system` and `update_atlas_content_size_system` generate a new `Measure` when a `ContentSize` component is added. --- crates/bevy_ui/src/layout/mod.rs | 10 +++++----- crates/bevy_ui/src/widget/image.rs | 11 +++++++++-- crates/bevy_ui/src/widget/text.rs | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index a8ea44f664b4e..80cd0c9f86092 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -274,6 +274,11 @@ pub fn ui_layout_system( } } + // When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node. + for entity in removed_content_sizes.read() { + ui_surface.try_remove_measure(entity); + } + for (entity, mut content_size) in measure_query.iter_mut() { if let Some(measure_func) = content_size.measure_func.take() { ui_surface.update_measure(entity, measure_func); @@ -283,11 +288,6 @@ pub fn ui_layout_system( // clean up removed nodes ui_surface.remove_entities(removed_nodes.read()); - // When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node. - for entity in removed_content_sizes.read() { - ui_surface.try_remove_measure(entity); - } - // update window children (for now assuming all Nodes live in the primary window) ui_surface.set_window_children(primary_window_entity, root_node_query.iter()); diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 022c174adf462..f7a54c0da6f42 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -3,6 +3,7 @@ use crate::{ }; use bevy_asset::{Assets, Handle}; +use bevy_ecs::change_detection::DetectChanges; use bevy_ecs::query::Without; use bevy_ecs::{ prelude::Component, @@ -96,7 +97,10 @@ pub fn update_image_content_size_system( texture.texture_descriptor.size.height as f32, ); // Update only if size or scale factor has changed to avoid needless layout calculations - if size != image_size.size || combined_scale_factor != *previous_combined_scale_factor { + if size != image_size.size + || combined_scale_factor != *previous_combined_scale_factor + || content_size.is_added() + { image_size.size = size; content_size.set(ImageMeasure { // multiply the image size by the scale factor to get the physical size @@ -135,7 +139,10 @@ pub fn update_atlas_content_size_system( if let Some(atlas) = atlases.get(atlas) { let size = atlas.textures[atlas_image.index].size(); // Update only if size or scale factor has changed to avoid needless layout calculations - if size != image_size.size || combined_scale_factor != *previous_combined_scale_factor { + if size != image_size.size + || combined_scale_factor != *previous_combined_scale_factor + || content_size.is_added() + { image_size.size = size; content_size.set(ImageMeasure { // multiply the image size by the scale factor to get the physical size diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 9700d77c967bd..0f96057eafa9a 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -128,7 +128,7 @@ pub fn measure_text_system( if *last_scale_factor == scale_factor { // scale factor unchanged, only create new measure funcs for modified text for (text, content_size, text_flags) in text_query.iter_mut() { - if text.is_changed() || text_flags.needs_new_measure_func { + if text.is_changed() || text_flags.needs_new_measure_func || content_size.is_added() { create_text_measure(&fonts, scale_factor, text, content_size, text_flags); } }