diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index d59d8c544164c..1ce0cf8af0213 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -97,38 +97,35 @@ impl FlexSurface { &mut self, entity: Entity, style: &Style, - calculated_size: CalculatedSize, + calculated_size: &CalculatedSize, context: &LayoutContext, ) { let taffy = &mut self.taffy; let taffy_style = convert::from_style(context, style); let scale_factor = context.scale_factor; + let m = calculated_size.measure.dyn_clone(); let measure = taffy::node::MeasureFunc::Boxed(Box::new( - move |constraints: Size>, _available: Size| { - let mut size = Size { - width: (scale_factor * calculated_size.size.x as f64) as f32, - height: (scale_factor * calculated_size.size.y as f64) as f32, - }; - match (constraints.width, constraints.height) { - (None, None) => {} - (Some(width), None) => { - if calculated_size.preserve_aspect_ratio { - size.height = width * size.height / size.width; + move |constraints: Size>, available_space: Size| { + let size = m.measure( + constraints.width.map(|w| (w as f64 / scale_factor) as f32), + constraints.height.map(|h| (h as f64 / scale_factor) as f32), + match available_space.width { + AvailableSpace::Definite(w) => { + AvailableSpace::Definite((w as f64 / scale_factor) as f32) } - size.width = width; - } - (None, Some(height)) => { - if calculated_size.preserve_aspect_ratio { - size.width = height * size.width / size.height; + s => s, + }, + match available_space.height { + AvailableSpace::Definite(h) => { + AvailableSpace::Definite((h as f64 / scale_factor) as f32) } - size.height = height; - } - (Some(width), Some(height)) => { - size.width = width; - size.height = height; - } + s => s, + }, + ); + taffy::geometry::Size { + width: (size.x as f64 * scale_factor) as f32, + height: (size.y as f64 * scale_factor) as f32, } - size }, )); if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { @@ -307,7 +304,7 @@ pub fn flex_node_system( for (entity, style, calculated_size) in &query { // TODO: remove node from old hierarchy if its root has changed if let Some(calculated_size) = calculated_size { - flex_surface.upsert_leaf(entity, style, *calculated_size, viewport_values); + flex_surface.upsert_leaf(entity, style, calculated_size, viewport_values); } else { flex_surface.upsert_node(entity, style, viewport_values); } @@ -322,7 +319,7 @@ pub fn flex_node_system( } for (entity, style, calculated_size) in &changed_size_query { - flex_surface.upsert_leaf(entity, style, *calculated_size, &viewport_values); + flex_surface.upsert_leaf(entity, style, calculated_size, &viewport_values); } // clean up removed nodes diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index fe63b84670d96..ce77f0229edc0 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -14,6 +14,7 @@ mod ui_node; #[cfg(feature = "bevy_text")] mod accessibility; pub mod camera_config; +pub mod measurement; pub mod node_bundles; pub mod update; pub mod widget; @@ -24,6 +25,7 @@ use bevy_render::extract_component::ExtractComponentPlugin; pub use flex::*; pub use focus::*; pub use geometry::*; +pub use measurement::*; pub use render::*; pub use ui_node::*; @@ -31,7 +33,8 @@ pub use ui_node::*; pub mod prelude { #[doc(hidden)] pub use crate::{ - camera_config::*, geometry::*, node_bundles::*, ui_node::*, widget::*, Interaction, UiScale, + camera_config::*, geometry::*, measurement::*, node_bundles::*, ui_node::*, widget::*, + Interaction, UiScale, }; } @@ -85,7 +88,6 @@ impl Plugin for UiPlugin { .register_type::() .register_type::() .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_ui/src/measurement.rs b/crates/bevy_ui/src/measurement.rs new file mode 100644 index 0000000000000..01b908a987b9d --- /dev/null +++ b/crates/bevy_ui/src/measurement.rs @@ -0,0 +1,104 @@ +use bevy_ecs::prelude::Component; +use bevy_math::Vec2; +use std::fmt::Formatter; +pub use taffy::style::AvailableSpace; + +/// The calculated size of the node +#[derive(Component)] +pub struct CalculatedSize { + pub size: Vec2, + /// The measure function used to calculate the size + pub measure: Box, +} + +impl Clone for CalculatedSize { + fn clone(&self) -> Self { + Self { + size: self.size.clone(), + measure: self.measure.dyn_clone(), + } + } +} + +impl Default for CalculatedSize { + fn default() -> Self { + Self { + size: Default::default(), + measure: Box::new(|w: Option, h: Option, _, _| { + Vec2::new(w.unwrap_or(0.), h.unwrap_or(0.)) + }), + } + } +} + +impl std::fmt::Debug for CalculatedSize { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CalculatedSize") + .field("size", &self.size) + .finish() + } +} + +pub trait NodeMeasure: Send + Sync + 'static { + /// Calculate the size of the node given the constraints. + fn measure( + &self, + width: Option, + height: Option, + available_width: AvailableSpace, + available_height: AvailableSpace, + ) -> Vec2; + + /// Clone and box self. + fn dyn_clone(&self) -> Box; +} + +#[derive(Clone)] +pub struct BasicMeasure { + /// Prefered size + pub size: Vec2, +} + +impl NodeMeasure for BasicMeasure { + fn measure( + &self, + width: Option, + height: Option, + _: AvailableSpace, + _: AvailableSpace, + ) -> Vec2 { + match (width, height) { + (Some(width), Some(height)) => Vec2::new(width, height), + (Some(width), None) => Vec2::new(width, self.size.y), + (None, Some(height)) => Vec2::new(self.size.x, height), + (None, None) => self.size, + } + } + + fn dyn_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +impl NodeMeasure for F +where + F: Fn(Option, Option, AvailableSpace, AvailableSpace) -> Vec2 + + Send + + Sync + + 'static + + Clone, +{ + fn measure( + &self, + width: Option, + height: Option, + available_width: AvailableSpace, + available_height: AvailableSpace, + ) -> Vec2 { + self(width, height, available_width, available_height) + } + + fn dyn_clone(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index e0eee6a61062a..29a2090366474 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -62,7 +62,7 @@ impl Default for NodeBundle { } /// A UI node that is an image -#[derive(Bundle, Clone, Debug, Default)] +#[derive(Bundle, Debug, Default)] pub struct ImageBundle { /// Describes the logical size of the node pub node: Node, diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index d51e2c8a75f34..a4e2668f00312 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -682,29 +682,6 @@ impl Default for FlexWrap { } } -/// The calculated size of the node -#[derive(Component, Copy, Clone, Debug, Reflect)] -#[reflect(Component)] -pub struct CalculatedSize { - /// The size of the node in logical pixels - pub size: Vec2, - /// Whether to attempt to preserve the aspect ratio when determining the layout for this item - pub preserve_aspect_ratio: bool, -} - -impl CalculatedSize { - const DEFAULT: Self = Self { - size: Vec2::ZERO, - preserve_aspect_ratio: false, - }; -} - -impl Default for CalculatedSize { - fn default() -> Self { - Self::DEFAULT - } -} - /// The background color of the node /// /// This serves as the "fill" color. diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 4b5777c841dd4..7305a3630c495 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -1,4 +1,4 @@ -use crate::{CalculatedSize, UiImage}; +use crate::{CalculatedSize, NodeMeasure, UiImage}; use bevy_asset::Assets; #[cfg(feature = "bevy_text")] use bevy_ecs::query::Without; @@ -7,6 +7,44 @@ use bevy_math::Vec2; use bevy_render::texture::Image; #[cfg(feature = "bevy_text")] use bevy_text::Text; +use taffy::prelude::AvailableSpace; + +#[derive(Clone)] +pub struct ImageMeasure { + size: Vec2, +} + +impl NodeMeasure for ImageMeasure { + fn measure( + &self, + width: Option, + height: Option, + _: AvailableSpace, + _: AvailableSpace, + ) -> Vec2 { + let mut size = self.size; + match (width, height) { + (None, None) => {} + (Some(width), None) => { + size.y = width * size.y / size.x; + size.x = width; + } + (None, Some(height)) => { + size.x = height * size.x / size.y; + size.y = height; + } + (Some(width), Some(height)) => { + size.x = width; + size.y = height; + } + } + size + } + + fn dyn_clone(&self) -> Box { + Box::new(self.clone()) + } +} /// Updates calculated size of the node based on the image provided pub fn update_image_calculated_size_system( @@ -23,7 +61,7 @@ pub fn update_image_calculated_size_system( // Update only if size has changed to avoid needless layout calculations if size != calculated_size.size { calculated_size.size = size; - calculated_size.preserve_aspect_ratio = true; + calculated_size.measure = Box::new(ImageMeasure { size }); } } } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 97a7c7b82137f..b4b0ea44b2798 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -1,4 +1,4 @@ -use crate::{CalculatedSize, Node, Style, UiScale, Val}; +use crate::{BasicMeasure, CalculatedSize, Node, Style, UiScale, Val}; use bevy_asset::Assets; use bevy_ecs::{ entity::Entity, @@ -133,10 +133,12 @@ pub fn text_system( panic!("Fatal error when processing text: {e}."); } Ok(info) => { - calculated_size.size = Vec2::new( - scale_value(info.size.x, inv_scale_factor), - scale_value(info.size.y, inv_scale_factor), - ); + calculated_size.measure = Box::new(BasicMeasure { + size: Vec2::new( + scale_value(info.size.x, inv_scale_factor), + scale_value(info.size.y, inv_scale_factor), + ), + }); match text_layout_info { Some(mut t) => *t = info, None => {