Skip to content

Commit ba532e4

Browse files
authored
MeasureFunc improvements (#8402)
# Objective fixes #8516 * Give `CalculatedSize` a more specific and intuitive name. * `MeasureFunc`s should only be updated when their `CalculatedSize` is modified by the systems managing their content. For example, suppose that you have a UI displaying an image using an `ImageNode`. When the window is resized, the node's `MeasureFunc` will be updated even though the dimensions of the texture contained by the node are unchanged. * Fix the `CalculatedSize` API so that it no longer requires the extra boxing and the `dyn_clone` method. ## Solution * Rename `CalculatedSize` to `ContentSize` * Only update `MeasureFunc`s on `CalculatedSize` changes. * Remove the `dyn_clone` method from `Measure` and move the `Measure` from the `ContentSize` component rather than cloning it. * Change the measure_func field of `ContentSize` to type `Option<taffy::node::MeasureFunc>`. Add a `set` method that wraps the given measure appropriately. --- ## Changelog * Renamed `CalculatedSize` to `ContentSize`. * Replaced `upsert_leaf` with a function `update_measure` that only updates the node's `MeasureFunc`. * `MeasureFunc`s are only updated when the `ContentSize` changes and not when the layout changes. * Scale factor is no longer applied to the size values passed to the `MeasureFunc`. * Remove the `ContentSize` scaling in `text_system`. * The `dyn_clone` method has been removed from the `Measure` trait. * `Measure`s are moved from the `ContentSize` component instead of cloning them. * Added `set` method to `ContentSize` that replaces the `new` function. ## Migration Guide * `CalculatedSize` has been renamed to `ContentSize`. * The `upsert_leaf` function has been removed from `UiSurface` and replaced with `update_measure` which updates the `MeasureFunc` without node insertion. * The `dyn_clone` method has been removed from the `Measure` trait. * The new function of `CalculatedSize` has been replaced with the method `set`.
1 parent deba380 commit ba532e4

File tree

6 files changed

+78
-114
lines changed

6 files changed

+78
-114
lines changed

crates/bevy_ui/src/layout/mod.rs

+33-66
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
mod convert;
22

3-
use crate::{CalculatedSize, Node, Style, UiScale};
3+
use crate::{ContentSize, Node, Style, UiScale};
44
use bevy_ecs::{
55
change_detection::DetectChanges,
66
entity::Entity,
77
event::EventReader,
8-
query::{Changed, Or, With, Without},
8+
query::{Changed, With, Without},
99
removal_detection::RemovedComponents,
1010
system::{Query, Res, ResMut, Resource},
11+
world::Ref,
1112
};
1213
use bevy_hierarchy::{Children, Parent};
1314
use bevy_log::warn;
@@ -16,11 +17,7 @@ use bevy_transform::components::Transform;
1617
use bevy_utils::HashMap;
1718
use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged};
1819
use std::fmt;
19-
use taffy::{
20-
prelude::{AvailableSpace, Size},
21-
style_helpers::TaffyMaxContent,
22-
Taffy,
23-
};
20+
use taffy::{prelude::Size, style_helpers::TaffyMaxContent, Taffy};
2421

2522
pub struct LayoutContext {
2623
pub scale_factor: f64,
@@ -75,6 +72,8 @@ impl Default for UiSurface {
7572
}
7673

7774
impl UiSurface {
75+
/// Retrieves the taffy node corresponding to given entity exists, or inserts a new taffy node into the layout if no corresponding node exists.
76+
/// Then convert the given `Style` and use it update the taffy node's style.
7877
pub fn upsert_node(&mut self, entity: Entity, style: &Style, context: &LayoutContext) {
7978
let mut added = false;
8079
let taffy = &mut self.taffy;
@@ -90,43 +89,13 @@ impl UiSurface {
9089
}
9190
}
9291

93-
pub fn upsert_leaf(
94-
&mut self,
95-
entity: Entity,
96-
style: &Style,
97-
calculated_size: &CalculatedSize,
98-
context: &LayoutContext,
99-
) {
100-
let taffy = &mut self.taffy;
101-
let taffy_style = convert::from_style(context, style);
102-
let measure = calculated_size.measure.dyn_clone();
103-
let measure_func = taffy::node::MeasureFunc::Boxed(Box::new(
104-
move |constraints: Size<Option<f32>>, available: Size<AvailableSpace>| {
105-
let size = measure.measure(
106-
constraints.width,
107-
constraints.height,
108-
available.width,
109-
available.height,
110-
);
111-
taffy::geometry::Size {
112-
width: size.x,
113-
height: size.y,
114-
}
115-
},
116-
));
117-
if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
118-
self.taffy.set_style(*taffy_node, taffy_style).unwrap();
119-
self.taffy
120-
.set_measure(*taffy_node, Some(measure_func))
121-
.unwrap();
122-
} else {
123-
let taffy_node = taffy
124-
.new_leaf_with_measure(taffy_style, measure_func)
125-
.unwrap();
126-
self.entity_to_taffy.insert(entity, taffy_node);
127-
}
92+
/// Update the `MeasureFunc` of the taffy node corresponding to the given [`Entity`].
93+
pub fn update_measure(&mut self, entity: Entity, measure_func: taffy::node::MeasureFunc) {
94+
let taffy_node = self.entity_to_taffy.get(&entity).unwrap();
95+
self.taffy.set_measure(*taffy_node, Some(measure_func)).ok();
12896
}
12997

98+
/// Update the children of the taffy node corresponding to the given [`Entity`].
13099
pub fn update_children(&mut self, entity: Entity, children: &Children) {
131100
let mut taffy_children = Vec::with_capacity(children.len());
132101
for child in children {
@@ -160,6 +129,7 @@ without UI components as a child of an entity with UI components, results may be
160129
}
161130
}
162131

132+
/// Retrieve or insert the root layout node and update its size to match the size of the window.
163133
pub fn update_window(&mut self, window: Entity, window_resolution: &WindowResolution) {
164134
let taffy = &mut self.taffy;
165135
let node = self
@@ -185,6 +155,7 @@ without UI components as a child of an entity with UI components, results may be
185155
.unwrap();
186156
}
187157

158+
/// Set the ui node entities without a [`Parent`] as children to the root node in the taffy layout.
188159
pub fn set_window_children(
189160
&mut self,
190161
parent_window: Entity,
@@ -197,6 +168,7 @@ without UI components as a child of an entity with UI components, results may be
197168
self.taffy.set_children(*taffy_node, &child_nodes).unwrap();
198169
}
199170

171+
/// Compute the layout for each window entity's corresponding root node in the layout.
200172
pub fn compute_window_layouts(&mut self) {
201173
for window_node in self.window_nodes.values() {
202174
self.taffy
@@ -214,6 +186,8 @@ without UI components as a child of an entity with UI components, results may be
214186
}
215187
}
216188

189+
/// Get the layout geometry for the taffy node corresponding to the ui node [`Entity`].
190+
/// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function.
217191
pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, LayoutError> {
218192
if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
219193
self.taffy
@@ -235,6 +209,7 @@ pub enum LayoutError {
235209
TaffyError(taffy::error::TaffyError),
236210
}
237211

212+
/// Updates the UI's layout tree, computes the new layout geometry and then updates the sizes and transforms of all the UI nodes.
238213
#[allow(clippy::too_many_arguments)]
239214
pub fn ui_layout_system(
240215
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
@@ -244,18 +219,11 @@ pub fn ui_layout_system(
244219
mut resize_events: EventReader<bevy_window::WindowResized>,
245220
mut ui_surface: ResMut<UiSurface>,
246221
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
247-
full_node_query: Query<(Entity, &Style, Option<&CalculatedSize>), With<Node>>,
248-
changed_style_query: Query<
249-
(Entity, &Style),
250-
(With<Node>, Without<CalculatedSize>, Changed<Style>),
251-
>,
252-
changed_size_query: Query<
253-
(Entity, &Style, &CalculatedSize),
254-
(With<Node>, Or<(Changed<CalculatedSize>, Changed<Style>)>),
255-
>,
222+
style_query: Query<(Entity, Ref<Style>), With<Node>>,
223+
mut measure_query: Query<(Entity, &mut ContentSize)>,
256224
children_query: Query<(Entity, &Children), (With<Node>, Changed<Children>)>,
257225
mut removed_children: RemovedComponents<Children>,
258-
mut removed_calculated_sizes: RemovedComponents<CalculatedSize>,
226+
mut removed_content_sizes: RemovedComponents<ContentSize>,
259227
mut node_transform_query: Query<(Entity, &mut Node, &mut Transform, Option<&Parent>)>,
260228
mut removed_nodes: RemovedComponents<Node>,
261229
) {
@@ -285,35 +253,34 @@ pub fn ui_layout_system(
285253
}
286254

287255
let scale_factor = logical_to_physical_factor * ui_scale.scale;
256+
288257
let layout_context = LayoutContext::new(scale_factor, physical_size);
289258

290259
if !scale_factor_events.is_empty() || ui_scale.is_changed() || resized {
291260
scale_factor_events.clear();
292261
// update all nodes
293-
for (entity, style, calculated_size) in &full_node_query {
294-
if let Some(calculated_size) = calculated_size {
295-
ui_surface.upsert_leaf(entity, style, calculated_size, &layout_context);
296-
} else {
297-
ui_surface.upsert_node(entity, style, &layout_context);
298-
}
262+
for (entity, style) in style_query.iter() {
263+
ui_surface.upsert_node(entity, &style, &layout_context);
299264
}
300265
} else {
301-
// update changed nodes without a calculated size
302-
for (entity, style) in changed_style_query.iter() {
303-
ui_surface.upsert_node(entity, style, &layout_context);
266+
for (entity, style) in style_query.iter() {
267+
if style.is_changed() {
268+
ui_surface.upsert_node(entity, &style, &layout_context);
269+
}
304270
}
271+
}
305272

306-
// update changed nodes with a calculated size
307-
for (entity, style, calculated_size) in changed_size_query.iter() {
308-
ui_surface.upsert_leaf(entity, style, calculated_size, &layout_context);
273+
for (entity, mut content_size) in measure_query.iter_mut() {
274+
if let Some(measure_func) = content_size.measure_func.take() {
275+
ui_surface.update_measure(entity, measure_func);
309276
}
310277
}
311278

312279
// clean up removed nodes
313280
ui_surface.remove_entities(removed_nodes.iter());
314281

315-
// When a `CalculatedSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node.
316-
for entity in removed_calculated_sizes.iter() {
282+
// When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node.
283+
for entity in removed_content_sizes.iter() {
317284
ui_surface.try_remove_measure(entity);
318285
}
319286

crates/bevy_ui/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl Plugin for UiPlugin {
8888
.register_type::<AlignContent>()
8989
.register_type::<AlignItems>()
9090
.register_type::<AlignSelf>()
91-
.register_type::<CalculatedSize>()
91+
.register_type::<ContentSize>()
9292
.register_type::<Direction>()
9393
.register_type::<Display>()
9494
.register_type::<FlexDirection>()
@@ -144,7 +144,7 @@ impl Plugin for UiPlugin {
144144
#[cfg(feature = "bevy_text")]
145145
app.add_plugin(accessibility::AccessibilityPlugin);
146146
app.add_systems(PostUpdate, {
147-
let system = widget::update_image_calculated_size_system.before(UiSystem::Layout);
147+
let system = widget::update_image_content_size_system.before(UiSystem::Layout);
148148
// Potential conflicts: `Assets<Image>`
149149
// They run independently since `widget::image_node_system` will only ever observe
150150
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`

crates/bevy_ui/src/measurement.rs

+27-22
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use bevy_ecs::prelude::Component;
2+
use bevy_ecs::reflect::ReflectComponent;
23
use bevy_math::Vec2;
34
use bevy_reflect::Reflect;
45
use std::fmt::Formatter;
56
pub use taffy::style::AvailableSpace;
67

7-
impl std::fmt::Debug for CalculatedSize {
8+
impl std::fmt::Debug for ContentSize {
89
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
9-
f.debug_struct("CalculatedSize").finish()
10+
f.debug_struct("ContentSize").finish()
1011
}
1112
}
1213

@@ -21,9 +22,6 @@ pub trait Measure: Send + Sync + 'static {
2122
available_width: AvailableSpace,
2223
available_height: AvailableSpace,
2324
) -> Vec2;
24-
25-
/// Clone and box self.
26-
fn dyn_clone(&self) -> Box<dyn Measure>;
2725
}
2826

2927
/// A `FixedMeasure` is a `Measure` that ignores all constraints and
@@ -43,35 +41,42 @@ impl Measure for FixedMeasure {
4341
) -> Vec2 {
4442
self.size
4543
}
46-
47-
fn dyn_clone(&self) -> Box<dyn Measure> {
48-
Box::new(self.clone())
49-
}
5044
}
5145

52-
/// A node with a `CalculatedSize` component is a node where its size
46+
/// A node with a `ContentSize` component is a node where its size
5347
/// is based on its content.
5448
#[derive(Component, Reflect)]
55-
pub struct CalculatedSize {
49+
#[reflect(Component)]
50+
pub struct ContentSize {
5651
/// The `Measure` used to compute the intrinsic size
5752
#[reflect(ignore)]
58-
pub measure: Box<dyn Measure>,
53+
pub(crate) measure_func: Option<taffy::node::MeasureFunc>,
5954
}
6055

61-
#[allow(clippy::derivable_impls)]
62-
impl Default for CalculatedSize {
63-
fn default() -> Self {
64-
Self {
65-
// Default `FixedMeasure` always returns zero size.
66-
measure: Box::<FixedMeasure>::default(),
67-
}
56+
impl ContentSize {
57+
/// Set a `Measure` for this function
58+
pub fn set(&mut self, measure: impl Measure) {
59+
let measure_func =
60+
move |size: taffy::prelude::Size<Option<f32>>,
61+
available: taffy::prelude::Size<AvailableSpace>| {
62+
let size =
63+
measure.measure(size.width, size.height, available.width, available.height);
64+
taffy::prelude::Size {
65+
width: size.x,
66+
height: size.y,
67+
}
68+
};
69+
self.measure_func = Some(taffy::node::MeasureFunc::Boxed(Box::new(measure_func)));
6870
}
6971
}
7072

71-
impl Clone for CalculatedSize {
72-
fn clone(&self) -> Self {
73+
#[allow(clippy::derivable_impls)]
74+
impl Default for ContentSize {
75+
fn default() -> Self {
7376
Self {
74-
measure: self.measure.dyn_clone(),
77+
measure_func: Some(taffy::node::MeasureFunc::Raw(|_, _| {
78+
taffy::prelude::Size::ZERO
79+
})),
7580
}
7681
}
7782
}

crates/bevy_ui/src/node_bundles.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
widget::{Button, UiImageSize},
5-
BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
5+
BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
66
};
77
use bevy_ecs::bundle::Bundle;
88
use bevy_render::{
@@ -63,7 +63,7 @@ impl Default for NodeBundle {
6363
}
6464

6565
/// A UI node that is an image
66-
#[derive(Bundle, Clone, Debug, Default)]
66+
#[derive(Bundle, Debug, Default)]
6767
pub struct ImageBundle {
6868
/// Describes the logical size of the node
6969
///
@@ -74,7 +74,7 @@ pub struct ImageBundle {
7474
/// In some cases these styles also affect how the node drawn/painted.
7575
pub style: Style,
7676
/// The calculated size based on the given image
77-
pub calculated_size: CalculatedSize,
77+
pub calculated_size: ContentSize,
7878
/// The background color, which serves as a "fill" for this node
7979
///
8080
/// Combines with `UiImage` to tint the provided image.
@@ -107,7 +107,7 @@ pub struct ImageBundle {
107107

108108
#[cfg(feature = "bevy_text")]
109109
/// A UI node that is text
110-
#[derive(Bundle, Clone, Debug)]
110+
#[derive(Bundle, Debug)]
111111
pub struct TextBundle {
112112
/// Describes the logical size of the node
113113
pub node: Node,
@@ -119,7 +119,7 @@ pub struct TextBundle {
119119
/// Text layout information
120120
pub text_layout_info: TextLayoutInfo,
121121
/// The calculated size based on the given image
122-
pub calculated_size: CalculatedSize,
122+
pub calculated_size: ContentSize,
123123
/// Whether this node should block interaction with lower nodes
124124
pub focus_policy: FocusPolicy,
125125
/// The transform of the node

crates/bevy_ui/src/widget/image.rs

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{measurement::AvailableSpace, CalculatedSize, Measure, Node, UiImage};
1+
use crate::{measurement::AvailableSpace, ContentSize, Measure, Node, UiImage};
22
use bevy_asset::Assets;
33
#[cfg(feature = "bevy_text")]
44
use bevy_ecs::query::Without;
@@ -61,25 +61,21 @@ impl Measure for ImageMeasure {
6161
}
6262
size
6363
}
64-
65-
fn dyn_clone(&self) -> Box<dyn Measure> {
66-
Box::new(self.clone())
67-
}
6864
}
6965

70-
/// Updates calculated size of the node based on the image provided
71-
pub fn update_image_calculated_size_system(
66+
/// Updates content size of the node based on the image provided
67+
pub fn update_image_content_size_system(
7268
textures: Res<Assets<Image>>,
7369
#[cfg(feature = "bevy_text")] mut query: Query<
74-
(&mut CalculatedSize, &UiImage, &mut UiImageSize),
70+
(&mut ContentSize, &UiImage, &mut UiImageSize),
7571
(With<Node>, Without<Text>),
7672
>,
7773
#[cfg(not(feature = "bevy_text"))] mut query: Query<
78-
(&mut CalculatedSize, &UiImage, &mut UiImageSize),
74+
(&mut ContentSize, &UiImage, &mut UiImageSize),
7975
With<Node>,
8076
>,
8177
) {
82-
for (mut calculated_size, image, mut image_size) in &mut query {
78+
for (mut content_size, image, mut image_size) in &mut query {
8379
if let Some(texture) = textures.get(&image.texture) {
8480
let size = Vec2::new(
8581
texture.texture_descriptor.size.width as f32,
@@ -88,7 +84,7 @@ pub fn update_image_calculated_size_system(
8884
// Update only if size has changed to avoid needless layout calculations
8985
if size != image_size.size {
9086
image_size.size = size;
91-
calculated_size.measure = Box::new(ImageMeasure { size });
87+
content_size.set(ImageMeasure { size });
9288
}
9389
}
9490
}

0 commit comments

Comments
 (0)