diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 2f1f6a8eaf973..4a4768a865280 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -34,7 +34,15 @@ bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev", default-fea ] } # other -taffy = { version = "0.7" } +taffy = { version = "0.9", default-features = false, features = [ + "std", + "block_layout", + "flexbox", + "grid", + "content_size", + "alloc", + "taffy_tree", +] } serde = { version = "1", features = ["derive"], optional = true } uuid = { version = "1.1", features = ["v4"], optional = true } thiserror = { version = "2", default-features = false } diff --git a/crates/bevy_ui/src/layout/convert.rs b/crates/bevy_ui/src/layout/convert.rs index 161fc12751650..b12cb0f17cfb3 100644 --- a/crates/bevy_ui/src/layout/convert.rs +++ b/crates/bevy_ui/src/layout/convert.rs @@ -1,4 +1,4 @@ -use taffy::style_helpers; +use taffy::{style_helpers, CheapCloneStr}; use crate::{ AlignContent, AlignItems, AlignSelf, BoxSizing, Display, FlexDirection, FlexWrap, GridAutoFlow, @@ -15,35 +15,33 @@ impl Val { context: &LayoutContext, ) -> taffy::style::LengthPercentageAuto { match self { - Val::Auto => taffy::style::LengthPercentageAuto::Auto, - Val::Percent(value) => taffy::style::LengthPercentageAuto::Percent(value / 100.), - Val::Px(value) => { - taffy::style::LengthPercentageAuto::Length(context.scale_factor * value) + Val::Auto => style_helpers::auto(), + Val::Percent(value) => style_helpers::percent(value / 100.), + Val::Px(value) => style_helpers::length(context.scale_factor * value), + Val::VMin(value) => { + style_helpers::length(context.physical_size.min_element() * value / 100.) } - Val::VMin(value) => taffy::style::LengthPercentageAuto::Length( - context.physical_size.min_element() * value / 100., - ), - Val::VMax(value) => taffy::style::LengthPercentageAuto::Length( - context.physical_size.max_element() * value / 100., - ), - Val::Vw(value) => { - taffy::style::LengthPercentageAuto::Length(context.physical_size.x * value / 100.) - } - Val::Vh(value) => { - taffy::style::LengthPercentageAuto::Length(context.physical_size.y * value / 100.) + Val::VMax(value) => { + style_helpers::length(context.physical_size.max_element() * value / 100.) } + Val::Vw(value) => style_helpers::length(context.physical_size.x * value / 100.), + Val::Vh(value) => style_helpers::length(context.physical_size.y * value / 100.), } } fn into_length_percentage(self, context: &LayoutContext) -> taffy::style::LengthPercentage { - match self.into_length_percentage_auto(context) { - taffy::style::LengthPercentageAuto::Auto => taffy::style::LengthPercentage::Length(0.0), - taffy::style::LengthPercentageAuto::Percent(value) => { - taffy::style::LengthPercentage::Percent(value) + match self { + Val::Auto => style_helpers::length(0.), + Val::Percent(value) => style_helpers::percent(value / 100.), + Val::Px(value) => style_helpers::length(context.scale_factor * value), + Val::VMin(value) => { + style_helpers::length(context.physical_size.min_element() * value / 100.) } - taffy::style::LengthPercentageAuto::Length(value) => { - taffy::style::LengthPercentage::Length(value) + Val::VMax(value) => { + style_helpers::length(context.physical_size.max_element() * value / 100.) } + Val::Vw(value) => style_helpers::length(context.physical_size.x * value / 100.), + Val::Vh(value) => style_helpers::length(context.physical_size.y * value / 100.), } } @@ -146,6 +144,7 @@ pub fn from_node(node: &Node, context: &LayoutContext, ignore_border: bool) -> t .collect::>(), grid_row: node.grid_row.into(), grid_column: node.grid_column.into(), + ..Default::default() } } @@ -335,26 +334,48 @@ impl From for taffy::geometry::Line impl MinTrackSizingFunction { fn into_taffy(self, context: &LayoutContext) -> taffy::style::MinTrackSizingFunction { match self { - MinTrackSizingFunction::Px(val) => taffy::style::MinTrackSizingFunction::Fixed( - Val::Px(val).into_length_percentage(context), + MinTrackSizingFunction::Px(val) => taffy::style::MinTrackSizingFunction::length( + Val::Px(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MinTrackSizingFunction::Percent(val) => taffy::style::MinTrackSizingFunction::Fixed( - Val::Percent(val).into_length_percentage(context), + MinTrackSizingFunction::Percent(val) => taffy::style::MinTrackSizingFunction::percent( + Val::Percent(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MinTrackSizingFunction::Auto => taffy::style::MinTrackSizingFunction::Auto, - MinTrackSizingFunction::MinContent => taffy::style::MinTrackSizingFunction::MinContent, - MinTrackSizingFunction::MaxContent => taffy::style::MinTrackSizingFunction::MaxContent, - MinTrackSizingFunction::VMin(val) => taffy::style::MinTrackSizingFunction::Fixed( - Val::VMin(val).into_length_percentage(context), + MinTrackSizingFunction::Auto => taffy::style::MinTrackSizingFunction::auto(), + MinTrackSizingFunction::MinContent => { + taffy::style::MinTrackSizingFunction::min_content() + } + MinTrackSizingFunction::MaxContent => { + taffy::style::MinTrackSizingFunction::max_content() + } + MinTrackSizingFunction::VMin(val) => taffy::style::MinTrackSizingFunction::length( + Val::VMin(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MinTrackSizingFunction::VMax(val) => taffy::style::MinTrackSizingFunction::Fixed( - Val::VMax(val).into_length_percentage(context), + MinTrackSizingFunction::VMax(val) => taffy::style::MinTrackSizingFunction::length( + Val::VMax(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MinTrackSizingFunction::Vh(val) => taffy::style::MinTrackSizingFunction::Fixed( - Val::Vh(val).into_length_percentage(context), + MinTrackSizingFunction::Vh(val) => taffy::style::MinTrackSizingFunction::length( + Val::Vh(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MinTrackSizingFunction::Vw(val) => taffy::style::MinTrackSizingFunction::Fixed( - Val::Vw(val).into_length_percentage(context), + MinTrackSizingFunction::Vw(val) => taffy::style::MinTrackSizingFunction::length( + Val::Vw(val) + .into_length_percentage(context) + .into_raw() + .value(), ), } } @@ -363,49 +384,74 @@ impl MinTrackSizingFunction { impl MaxTrackSizingFunction { fn into_taffy(self, context: &LayoutContext) -> taffy::style::MaxTrackSizingFunction { match self { - MaxTrackSizingFunction::Px(val) => taffy::style::MaxTrackSizingFunction::Fixed( - Val::Px(val).into_length_percentage(context), + MaxTrackSizingFunction::Px(val) => taffy::style::MaxTrackSizingFunction::length( + Val::Px(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MaxTrackSizingFunction::Percent(val) => taffy::style::MaxTrackSizingFunction::Fixed( - Val::Percent(val).into_length_percentage(context), + MaxTrackSizingFunction::Percent(val) => taffy::style::MaxTrackSizingFunction::percent( + Val::Percent(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MaxTrackSizingFunction::Auto => taffy::style::MaxTrackSizingFunction::Auto, - MaxTrackSizingFunction::MinContent => taffy::style::MaxTrackSizingFunction::MinContent, - MaxTrackSizingFunction::MaxContent => taffy::style::MaxTrackSizingFunction::MaxContent, + MaxTrackSizingFunction::Auto => taffy::style::MaxTrackSizingFunction::auto(), + MaxTrackSizingFunction::MinContent => { + taffy::style::MaxTrackSizingFunction::min_content() + } + MaxTrackSizingFunction::MaxContent => { + taffy::style::MaxTrackSizingFunction::max_content() + } MaxTrackSizingFunction::FitContentPx(val) => { - taffy::style::MaxTrackSizingFunction::FitContent( - Val::Px(val).into_length_percentage(context), + taffy::style::MaxTrackSizingFunction::fit_content_px( + Val::Px(val) + .into_length_percentage(context) + .into_raw() + .value(), ) } MaxTrackSizingFunction::FitContentPercent(val) => { - taffy::style::MaxTrackSizingFunction::FitContent( - Val::Percent(val).into_length_percentage(context), + taffy::style::MaxTrackSizingFunction::fit_content_percent( + Val::Percent(val) + .into_length_percentage(context) + .into_raw() + .value(), ) } MaxTrackSizingFunction::Fraction(fraction) => { - taffy::style::MaxTrackSizingFunction::Fraction(fraction) + taffy::style::MaxTrackSizingFunction::fr(fraction) } - MaxTrackSizingFunction::VMin(val) => taffy::style::MaxTrackSizingFunction::Fixed( - Val::VMin(val).into_length_percentage(context), + MaxTrackSizingFunction::VMin(val) => taffy::style::MaxTrackSizingFunction::length( + Val::VMin(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MaxTrackSizingFunction::VMax(val) => taffy::style::MaxTrackSizingFunction::Fixed( - Val::VMax(val).into_length_percentage(context), + MaxTrackSizingFunction::VMax(val) => taffy::style::MaxTrackSizingFunction::length( + Val::VMax(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MaxTrackSizingFunction::Vh(val) => taffy::style::MaxTrackSizingFunction::Fixed( - Val::Vh(val).into_length_percentage(context), + MaxTrackSizingFunction::Vh(val) => taffy::style::MaxTrackSizingFunction::length( + Val::Vh(val) + .into_length_percentage(context) + .into_raw() + .value(), ), - MaxTrackSizingFunction::Vw(val) => taffy::style::MaxTrackSizingFunction::Fixed( - Val::Vw(val).into_length_percentage(context), + MaxTrackSizingFunction::Vw(val) => taffy::style::MaxTrackSizingFunction::length( + Val::Vw(val) + .into_length_percentage(context) + .into_raw() + .value(), ), } } } impl GridTrack { - fn into_taffy_track( - self, - context: &LayoutContext, - ) -> taffy::style::NonRepeatedTrackSizingFunction { + fn into_taffy_track(self, context: &LayoutContext) -> taffy::style::TrackSizingFunction { let min = self.min_sizing_function.into_taffy(context); let max = self.max_sizing_function.into_taffy(context); style_helpers::minmax(min, max) @@ -413,15 +459,18 @@ impl GridTrack { } impl RepeatedGridTrack { - fn clone_into_repeated_taffy_track( + fn clone_into_repeated_taffy_track( &self, context: &LayoutContext, - ) -> taffy::style::TrackSizingFunction { + ) -> taffy::style::GridTemplateComponent + where + S: CheapCloneStr, + { if self.tracks.len() == 1 && self.repetition == GridTrackRepetition::Count(1) { let min = self.tracks[0].min_sizing_function.into_taffy(context); let max = self.tracks[0].max_sizing_function.into_taffy(context); let taffy_track = style_helpers::minmax(min, max); - taffy::style::TrackSizingFunction::Single(taffy_track) + taffy::GridTemplateComponent::Single(taffy_track) } else { let taffy_tracks: Vec<_> = self .tracks @@ -436,10 +485,10 @@ impl RepeatedGridTrack { match self.repetition { GridTrackRepetition::Count(count) => style_helpers::repeat(count, taffy_tracks), GridTrackRepetition::AutoFit => { - style_helpers::repeat(taffy::style::GridTrackRepetition::AutoFit, taffy_tracks) + style_helpers::repeat(taffy::style::RepetitionCount::AutoFit, taffy_tracks) } GridTrackRepetition::AutoFill => { - style_helpers::repeat(taffy::style::GridTrackRepetition::AutoFill, taffy_tracks) + style_helpers::repeat(taffy::style::RepetitionCount::AutoFill, taffy_tracks) } } } @@ -537,15 +586,15 @@ mod tests { ); assert_eq!( taffy_style.inset.right, - taffy::style::LengthPercentageAuto::Percent(0.5) + taffy::style::LengthPercentageAuto::percent(0.5) ); assert_eq!( taffy_style.inset.top, - taffy::style::LengthPercentageAuto::Length(12.) + taffy::style::LengthPercentageAuto::length(12.) ); assert_eq!( taffy_style.inset.bottom, - taffy::style::LengthPercentageAuto::Auto + taffy::style::LengthPercentageAuto::auto() ); assert_eq!( taffy_style.flex_direction, @@ -576,23 +625,23 @@ mod tests { ); assert_eq!( taffy_style.margin.right, - taffy::style::LengthPercentageAuto::Length(10.) + taffy::style::LengthPercentageAuto::length(10.) ); assert_eq!( taffy_style.margin.top, - taffy::style::LengthPercentageAuto::Percent(0.15) + taffy::style::LengthPercentageAuto::percent(0.15) ); assert_eq!( taffy_style.margin.bottom, - taffy::style::LengthPercentageAuto::Auto + taffy::style::LengthPercentageAuto::auto() ); assert_eq!( taffy_style.padding.left, - taffy::style::LengthPercentage::Percent(0.13) + taffy::style::LengthPercentage::percent(0.13) ); assert_eq!( taffy_style.padding.right, - taffy::style::LengthPercentage::Length(21.) + taffy::style::LengthPercentage::length(21.) ); assert_eq!( taffy_style.padding.top, @@ -604,7 +653,7 @@ mod tests { ); assert_eq!( taffy_style.border.left, - taffy::style::LengthPercentage::Length(14.) + taffy::style::LengthPercentage::length(14.) ); assert_eq!( taffy_style.border.right, @@ -613,16 +662,16 @@ mod tests { assert_eq!(taffy_style.border.top, taffy::style::LengthPercentage::ZERO); assert_eq!( taffy_style.border.bottom, - taffy::style::LengthPercentage::Percent(0.31) + taffy::style::LengthPercentage::percent(0.31) ); assert_eq!(taffy_style.flex_grow, 1.); assert_eq!(taffy_style.flex_shrink, 0.); assert_eq!(taffy_style.flex_basis, taffy::style::Dimension::ZERO); assert_eq!(taffy_style.size.width, taffy::style::Dimension::ZERO); - assert_eq!(taffy_style.size.height, taffy::style::Dimension::Auto); + assert_eq!(taffy_style.size.height, taffy::style::Dimension::auto()); assert_eq!(taffy_style.min_size.width, taffy::style::Dimension::ZERO); assert_eq!(taffy_style.min_size.height, taffy::style::Dimension::ZERO); - assert_eq!(taffy_style.max_size.width, taffy::style::Dimension::Auto); + assert_eq!(taffy_style.max_size.width, taffy::style::Dimension::auto()); assert_eq!(taffy_style.max_size.height, taffy::style::Dimension::ZERO); assert_eq!(taffy_style.aspect_ratio, None); assert_eq!(taffy_style.scrollbar_width, 7.); @@ -643,8 +692,8 @@ mod tests { assert_eq!( taffy_style.grid_auto_rows, vec![ - sh::fit_content(taffy::style::LengthPercentage::Length(10.0)), - sh::fit_content(taffy::style::LengthPercentage::Percent(0.25)), + sh::fit_content(taffy::style::LengthPercentage::length(10.0)), + sh::fit_content(taffy::style::LengthPercentage::percent(0.25)), sh::minmax(sh::length(0.0), sh::fr(2.0)), ] ); @@ -667,20 +716,19 @@ mod tests { use taffy::style::LengthPercentage; let context = LayoutContext::new(2.0, Vec2::new(800., 600.)); let cases = [ - (Val::Auto, LengthPercentage::Length(0.)), - (Val::Percent(1.), LengthPercentage::Percent(0.01)), - (Val::Px(1.), LengthPercentage::Length(2.)), - (Val::Vw(1.), LengthPercentage::Length(8.)), - (Val::Vh(1.), LengthPercentage::Length(6.)), - (Val::VMin(2.), LengthPercentage::Length(12.)), - (Val::VMax(2.), LengthPercentage::Length(16.)), + (Val::Auto, LengthPercentage::length(0.)), + (Val::Percent(1.), LengthPercentage::percent(0.01)), + (Val::Px(1.), LengthPercentage::length(2.)), + (Val::Vw(1.), LengthPercentage::length(8.)), + (Val::Vh(1.), LengthPercentage::length(6.)), + (Val::VMin(2.), LengthPercentage::length(12.)), + (Val::VMax(2.), LengthPercentage::length(16.)), ]; for (val, length) in cases { - assert!(match (val.into_length_percentage(&context), length) { - (LengthPercentage::Length(a), LengthPercentage::Length(b)) - | (LengthPercentage::Percent(a), LengthPercentage::Percent(b)) => - (a - b).abs() < 0.0001, - _ => false, + assert!({ + let lhs = val.into_length_percentage(&context).into_raw().value(); + let rhs = length.into_raw().value(); + (lhs - rhs).abs() < 0.0001 }); } } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 3de05629ff294..913c195d52208 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -66,7 +66,7 @@ pub enum LayoutError { #[error("Invalid hierarchy")] InvalidHierarchy, #[error("Taffy error: {0}")] - TaffyError(taffy::TaffyError), + TaffyError(taffy::tree::TaffyError), } /// Updates the UI's layout tree, computes the new layout geometry and then updates the sizes and transforms of all the UI nodes. diff --git a/crates/bevy_ui/src/layout/ui_surface.rs b/crates/bevy_ui/src/layout/ui_surface.rs index 2df6afa947dac..5b128ce71b610 100644 --- a/crates/bevy_ui/src/layout/ui_surface.rs +++ b/crates/bevy_ui/src/layout/ui_surface.rs @@ -1,4 +1,5 @@ use core::fmt; +use core::ops::{Deref, DerefMut}; use bevy_platform::collections::hash_map::Entry; use taffy::TaffyTree; @@ -30,18 +31,41 @@ impl From for LayoutNode { } } +pub(crate) struct BevyTaffyTree(TaffyTree); + +#[expect(unsafe_code, reason = "TaffyTree is safe as long as calc is not used")] +/// SAFETY: Taffy Tree becomes thread unsafe when you use the calc feature, which we do not implement +unsafe impl Send for BevyTaffyTree {} + +#[expect(unsafe_code, reason = "TaffyTree is safe as long as calc is not used")] +/// SAFETY: Taffy Tree becomes thread unsafe when you use the calc feature, which we do not implement +unsafe impl Sync for BevyTaffyTree {} + +impl Deref for BevyTaffyTree { + type Target = TaffyTree; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for BevyTaffyTree { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[derive(Resource)] pub struct UiSurface { pub root_entity_to_viewport_node: EntityHashMap, pub(super) entity_to_taffy: EntityHashMap, - pub(super) taffy: TaffyTree, + pub(super) taffy: BevyTaffyTree, taffy_children_scratch: Vec, } fn _assert_send_sync_ui_surface_impl_safe() { fn _assert_send_sync() {} _assert_send_sync::>(); - _assert_send_sync::>(); + _assert_send_sync::>(); _assert_send_sync::(); } @@ -56,7 +80,7 @@ impl fmt::Debug for UiSurface { impl Default for UiSurface { fn default() -> Self { - let taffy: TaffyTree = TaffyTree::new(); + let taffy: BevyTaffyTree = BevyTaffyTree(TaffyTree::new()); Self { root_entity_to_viewport_node: Default::default(), entity_to_taffy: Default::default(), @@ -166,8 +190,8 @@ impl UiSurface { // Note: Taffy percentages are floats ranging from 0.0 to 1.0. // So this is setting width:100% and height:100% size: taffy::geometry::Size { - width: taffy::style::Dimension::Percent(1.0), - height: taffy::style::Dimension::Percent(1.0), + width: taffy::style_helpers::percent(1.0), + height: taffy::style_helpers::percent(1.0), }, align_items: Some(taffy::style::AlignItems::Start), justify_items: Some(taffy::style::JustifyItems::Start), diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 92e951a263f9d..5b2c166eab4e7 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -205,6 +205,11 @@ pub struct ImageMeasure { pub size: Vec2, } +// NOOP function used to call into taffy API +fn resolve_calc(_calc_ptr: *const (), _parent_size: f32) -> f32 { + 0.0 +} + impl Measure for ImageMeasure { fn measure(&mut self, measure_args: MeasureArgs, style: &taffy::Style) -> Vec2 { let MeasureArgs { @@ -221,12 +226,24 @@ impl Measure for ImageMeasure { // Resolve styles let s_aspect_ratio = style.aspect_ratio; - let s_width = style.size.width.maybe_resolve(parent_width); - let s_min_width = style.min_size.width.maybe_resolve(parent_width); - let s_max_width = style.max_size.width.maybe_resolve(parent_width); - let s_height = style.size.height.maybe_resolve(parent_height); - let s_min_height = style.min_size.height.maybe_resolve(parent_height); - let s_max_height = style.max_size.height.maybe_resolve(parent_height); + let s_width = style.size.width.maybe_resolve(parent_width, resolve_calc); + let s_min_width = style + .min_size + .width + .maybe_resolve(parent_width, resolve_calc); + let s_max_width = style + .max_size + .width + .maybe_resolve(parent_width, resolve_calc); + let s_height = style.size.height.maybe_resolve(parent_height, resolve_calc); + let s_min_height = style + .min_size + .height + .maybe_resolve(parent_height, resolve_calc); + let s_max_height = style + .max_size + .height + .maybe_resolve(parent_height, resolve_calc); // Determine width and height from styles and known_sizes (if a size is available // from any of these sources)