From 96b9d0a7e203bc55aff4f8b14ac8a51a6945fea5 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Wed, 1 May 2024 02:13:17 +1200 Subject: [PATCH] Upgrade to Taffy 0.4 (#10690) # Objective - Enables support for `Display::Block` - Enables support for `Overflow::Hidden` - Allows for cleaner integration with text, image and other content layout. - Unblocks https://github.com/bevyengine/bevy/pull/8104 - Unlocks the possibility of Bevy creating a custom layout tree over which Taffy operates. - Enables #8808 / #10193 to remove a Mutex around the font system. ## Todo - [x] ~Fix rendering of text/images to account for padding/border on nodes (should size/position to content box rather than border box)~ In order get this into a mergeable state this PR instead zeroes out padding/border when syncing leaf node styles into Taffy to preserve the existing behaviour. https://github.com/bevyengine/bevy/issues/6879 can be fixed in a followup PR. ## Solution - Update the version of Taffy - Update code to work with the new version Note: Taffy 0.4 has not yet been released. This PR is being created in advance of the release to ensure that there are no blockers to upgrading once the release occurs. --- ## Changelog - Bevy now supports the `Display::Block` and `Overflow::Hidden` styles. --- crates/bevy_ui/Cargo.toml | 2 +- crates/bevy_ui/src/layout/convert.rs | 108 +++++++++++++++--------- crates/bevy_ui/src/layout/debug.rs | 14 +-- crates/bevy_ui/src/layout/mod.rs | 60 +++++++------ crates/bevy_ui/src/layout/ui_surface.rs | 103 ++++++++++++++++------ crates/bevy_ui/src/measurement.rs | 59 ++++++++++--- crates/bevy_ui/src/ui_node.rs | 10 ++- crates/bevy_ui/src/widget/image.rs | 9 +- crates/bevy_ui/src/widget/text.rs | 6 +- examples/ui/display_and_visibility.rs | 2 +- 10 files changed, 250 insertions(+), 123 deletions(-) diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index d7882c7da4d38..4e47819a6da6c 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -31,7 +31,7 @@ bevy_window = { path = "../bevy_window", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } # other -taffy = { version = "0.3.10" } +taffy = { version = "0.4" } serde = { version = "1", features = ["derive"], optional = true } bytemuck = { version = "1.5", features = ["derive"] } thiserror = "1.0.0" diff --git a/crates/bevy_ui/src/layout/convert.rs b/crates/bevy_ui/src/layout/convert.rs index ce93ae806a240..2cc7c3b32c0f3 100644 --- a/crates/bevy_ui/src/layout/convert.rs +++ b/crates/bevy_ui/src/layout/convert.rs @@ -3,8 +3,8 @@ use taffy::style_helpers; use crate::{ AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, GridAutoFlow, GridPlacement, GridTrack, GridTrackRepetition, JustifyContent, JustifyItems, JustifySelf, - MaxTrackSizingFunction, MinTrackSizingFunction, PositionType, RepeatedGridTrack, Style, UiRect, - Val, + MaxTrackSizingFunction, MinTrackSizingFunction, OverflowAxis, PositionType, RepeatedGridTrack, + Style, UiRect, Val, }; use super::LayoutContext; @@ -18,31 +18,31 @@ impl Val { Val::Auto => taffy::style::LengthPercentageAuto::Auto, Val::Percent(value) => taffy::style::LengthPercentageAuto::Percent(value / 100.), Val::Px(value) => { - taffy::style::LengthPercentageAuto::Points(context.scale_factor * value) + taffy::style::LengthPercentageAuto::Length(context.scale_factor * value) } Val::VMin(value) => { - taffy::style::LengthPercentageAuto::Points(context.min_size * value / 100.) + taffy::style::LengthPercentageAuto::Length(context.min_size * value / 100.) } Val::VMax(value) => { - taffy::style::LengthPercentageAuto::Points(context.max_size * value / 100.) + taffy::style::LengthPercentageAuto::Length(context.max_size * value / 100.) } Val::Vw(value) => { - taffy::style::LengthPercentageAuto::Points(context.physical_size.x * value / 100.) + taffy::style::LengthPercentageAuto::Length(context.physical_size.x * value / 100.) } Val::Vh(value) => { - taffy::style::LengthPercentageAuto::Points(context.physical_size.y * value / 100.) + taffy::style::LengthPercentageAuto::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::Points(0.0), + taffy::style::LengthPercentageAuto::Auto => taffy::style::LengthPercentage::Length(0.0), taffy::style::LengthPercentageAuto::Percent(value) => { taffy::style::LengthPercentage::Percent(value) } - taffy::style::LengthPercentageAuto::Points(value) => { - taffy::style::LengthPercentage::Points(value) + taffy::style::LengthPercentageAuto::Length(value) => { + taffy::style::LengthPercentage::Length(value) } } } @@ -63,9 +63,18 @@ impl UiRect { } } -pub fn from_style(context: &LayoutContext, style: &Style) -> taffy::style::Style { +pub fn from_style( + context: &LayoutContext, + style: &Style, + ignore_padding_and_border: bool, +) -> taffy::style::Style { taffy::style::Style { display: style.display.into(), + overflow: taffy::Point { + x: style.overflow.x.into(), + y: style.overflow.y.into(), + }, + scrollbar_width: 0.0, position: style.position_type.into(), flex_direction: style.flex_direction.into(), flex_wrap: style.flex_wrap.into(), @@ -75,7 +84,7 @@ pub fn from_style(context: &LayoutContext, style: &Style) -> taffy::style::Style justify_self: style.justify_self.into(), align_content: style.align_content.into(), justify_content: style.justify_content.into(), - inset: taffy::prelude::Rect { + inset: taffy::Rect { left: style.left.into_length_percentage_auto(context), right: style.right.into_length_percentage_auto(context), top: style.top.into_length_percentage_auto(context), @@ -84,29 +93,41 @@ pub fn from_style(context: &LayoutContext, style: &Style) -> taffy::style::Style margin: style .margin .map_to_taffy_rect(|m| m.into_length_percentage_auto(context)), - padding: style - .padding - .map_to_taffy_rect(|m| m.into_length_percentage(context)), - border: style - .border - .map_to_taffy_rect(|m| m.into_length_percentage(context)), + // Ignore padding for leaf nodes as it isn't implemented in the rendering engine. + // TODO: Implement rendering of padding for leaf nodes + padding: if ignore_padding_and_border { + taffy::Rect::zero() + } else { + style + .padding + .map_to_taffy_rect(|m| m.into_length_percentage(context)) + }, + // Ignore border for leaf nodes as it isn't implemented in the rendering engine. + // TODO: Implement rendering of border for leaf nodes + border: if ignore_padding_and_border { + taffy::Rect::zero() + } else { + style + .border + .map_to_taffy_rect(|m| m.into_length_percentage(context)) + }, flex_grow: style.flex_grow, flex_shrink: style.flex_shrink, flex_basis: style.flex_basis.into_dimension(context), - size: taffy::prelude::Size { + size: taffy::Size { width: style.width.into_dimension(context), height: style.height.into_dimension(context), }, - min_size: taffy::prelude::Size { + min_size: taffy::Size { width: style.min_width.into_dimension(context), height: style.min_height.into_dimension(context), }, - max_size: taffy::prelude::Size { + max_size: taffy::Size { width: style.max_width.into_dimension(context), height: style.max_height.into_dimension(context), }, aspect_ratio: style.aspect_ratio, - gap: taffy::prelude::Size { + gap: taffy::Size { width: style.column_gap.into_length_percentage(context), height: style.row_gap.into_length_percentage(context), }, @@ -231,11 +252,22 @@ impl From for taffy::style::Display { match value { Display::Flex => taffy::style::Display::Flex, Display::Grid => taffy::style::Display::Grid, + Display::Block => taffy::style::Display::Block, Display::None => taffy::style::Display::None, } } } +impl From for taffy::style::Overflow { + fn from(value: OverflowAxis) -> Self { + match value { + OverflowAxis::Visible => taffy::style::Overflow::Visible, + OverflowAxis::Clip => taffy::style::Overflow::Clip, + OverflowAxis::Hidden => taffy::style::Overflow::Hidden, + } + } +} + impl From for taffy::style::FlexDirection { fn from(value: FlexDirection) -> Self { match value { @@ -489,7 +521,7 @@ mod tests { grid_row: GridPlacement::span(3), }; let viewport_values = LayoutContext::new(1.0, bevy_math::Vec2::new(800., 600.)); - let taffy_style = from_style(&viewport_values, &bevy_style); + let taffy_style = from_style(&viewport_values, &bevy_style, false); assert_eq!(taffy_style.display, taffy::style::Display::Flex); assert_eq!(taffy_style.position, taffy::style::Position::Absolute); assert_eq!( @@ -502,7 +534,7 @@ mod tests { ); assert_eq!( taffy_style.inset.top, - taffy::style::LengthPercentageAuto::Points(12.) + taffy::style::LengthPercentageAuto::Length(12.) ); assert_eq!( taffy_style.inset.bottom, @@ -537,7 +569,7 @@ mod tests { ); assert_eq!( taffy_style.margin.right, - taffy::style::LengthPercentageAuto::Points(10.) + taffy::style::LengthPercentageAuto::Length(10.) ); assert_eq!( taffy_style.margin.top, @@ -553,7 +585,7 @@ mod tests { ); assert_eq!( taffy_style.padding.right, - taffy::style::LengthPercentage::Points(21.) + taffy::style::LengthPercentage::Length(21.) ); assert_eq!( taffy_style.padding.top, @@ -565,7 +597,7 @@ mod tests { ); assert_eq!( taffy_style.border.left, - taffy::style::LengthPercentage::Points(14.) + taffy::style::LengthPercentage::Length(14.) ); assert_eq!( taffy_style.border.right, @@ -594,18 +626,18 @@ mod tests { ); assert_eq!( taffy_style.grid_template_rows, - vec![sh::points(10.0), sh::percent(0.5), sh::fr(1.0)] + vec![sh::length(10.0), sh::percent(0.5), sh::fr(1.0)] ); assert_eq!( taffy_style.grid_template_columns, - vec![sh::repeat(5, vec![sh::points(10.0)])] + vec![sh::repeat(5, vec![sh::length(10.0)])] ); assert_eq!( taffy_style.grid_auto_rows, vec![ - sh::fit_content(taffy::style::LengthPercentage::Points(10.0)), + sh::fit_content(taffy::style::LengthPercentage::Length(10.0)), sh::fit_content(taffy::style::LengthPercentage::Percent(0.25)), - sh::minmax(sh::points(0.0), sh::fr(2.0)), + sh::minmax(sh::length(0.0), sh::fr(2.0)), ] ); assert_eq!( @@ -627,17 +659,17 @@ mod tests { use taffy::style::LengthPercentage; let context = LayoutContext::new(2.0, bevy_math::Vec2::new(800., 600.)); let cases = [ - (Val::Auto, LengthPercentage::Points(0.)), + (Val::Auto, LengthPercentage::Length(0.)), (Val::Percent(1.), LengthPercentage::Percent(0.01)), - (Val::Px(1.), LengthPercentage::Points(2.)), - (Val::Vw(1.), LengthPercentage::Points(8.)), - (Val::Vh(1.), LengthPercentage::Points(6.)), - (Val::VMin(2.), LengthPercentage::Points(12.)), - (Val::VMax(2.), LengthPercentage::Points(16.)), + (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::Points(a), LengthPercentage::Points(b)) + (LengthPercentage::Length(a), LengthPercentage::Length(b)) | (LengthPercentage::Percent(a), LengthPercentage::Percent(b)) => (a - b).abs() < 0.0001, _ => false, diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index 67e7a205b2915..47b02396a6816 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -1,7 +1,6 @@ use std::fmt::Write; -use taffy::prelude::Node; -use taffy::tree::LayoutTree; +use taffy::{NodeId, TraversePartialTree}; use bevy_ecs::prelude::Entity; use bevy_utils::HashMap; @@ -10,7 +9,7 @@ use crate::layout::ui_surface::UiSurface; /// Prints a debug representation of the computed layout of the UI layout tree for each window. pub fn print_ui_layout_tree(ui_surface: &UiSurface) { - let taffy_to_entity: HashMap = ui_surface + let taffy_to_entity: HashMap = ui_surface .entity_to_taffy .iter() .map(|(entity, node)| (*node, *entity)) @@ -35,9 +34,9 @@ pub fn print_ui_layout_tree(ui_surface: &UiSurface) { /// Recursively navigates the layout tree printing each node's information. fn print_node( ui_surface: &UiSurface, - taffy_to_entity: &HashMap, + taffy_to_entity: &HashMap, entity: Entity, - node: Node, + node: NodeId, has_sibling: bool, lines_string: String, acc: &mut String, @@ -46,13 +45,14 @@ fn print_node( let layout = tree.layout(node).unwrap(); let style = tree.style(node).unwrap(); - let num_children = tree.child_count(node).unwrap(); + let num_children = tree.child_count(node); let display_variant = match (num_children, style.display) { (_, taffy::style::Display::None) => "NONE", (0, _) => "LEAF", (_, taffy::style::Display::Flex) => "FLEX", (_, taffy::style::Display::Grid) => "GRID", + (_, taffy::style::Display::Block) => "BLOCK", }; let fork_string = if has_sibling { @@ -70,7 +70,7 @@ fn print_node( y = layout.location.y, width = layout.size.width, height = layout.size.height, - measured = if tree.needs_measure(node) { "measured" } else { "" } + measured = if tree.get_node_context(node).is_some() { "measured" } else { "" } ).ok(); let bar = if has_sibling { "│ " } else { " " }; let new_string = lines_string + bar; diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index ff82518677ef4..43d767acdecbc 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1,5 +1,6 @@ use thiserror::Error; +use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale}; use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, entity::Entity, @@ -18,8 +19,6 @@ use bevy_utils::{HashMap, HashSet}; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; use ui_surface::UiSurface; -use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale}; - mod convert; pub mod debug; pub(crate) mod ui_surface; @@ -60,7 +59,7 @@ pub enum LayoutError { #[error("Invalid hierarchy")] InvalidHierarchy, #[error("Taffy error: {0}")] - TaffyError(#[from] taffy::error::TaffyError), + TaffyError(#[from] taffy::TaffyError), } #[derive(SystemParam)] @@ -82,8 +81,15 @@ pub fn ui_layout_system( mut resize_events: EventReader, mut ui_surface: ResMut, root_node_query: Query<(Entity, Option<&TargetCamera>), (With, Without)>, - style_query: Query<(Entity, Ref