diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index 28798bb7b3..c47b2ca8e8 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -12,17 +12,19 @@ pub fn print_ui_layout_tree(ui_surface: &UiSurface) { .iter() .map(|(entity, node)| (*node, *entity)) .collect(); - for (&entity, &node) in &ui_surface.window_nodes { + for (&entity, roots) in &ui_surface.window_roots { let mut out = String::new(); - print_node( - ui_surface, - &taffy_to_entity, - entity, - node, - false, - String::new(), - &mut out, - ); + for root in roots { + print_node( + ui_surface, + &taffy_to_entity, + entity, + root.implicit_viewport_node, + false, + String::new(), + &mut out, + ); + } bevy_log::info!("Layout tree for window entity: {entity:?}\n{out}"); } } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 1ea53f3b3a..90603577b1 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -15,10 +15,10 @@ use bevy_hierarchy::{Children, Parent}; use bevy_log::warn; use bevy_math::Vec2; use bevy_transform::components::Transform; -use bevy_utils::HashMap; +use bevy_utils::{default, HashMap}; use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged}; use std::fmt; -use taffy::{prelude::Size, style_helpers::TaffyMaxContent, Taffy}; +use taffy::Taffy; pub struct LayoutContext { pub scale_factor: f64, @@ -39,10 +39,18 @@ impl LayoutContext { } } +#[derive(Debug, Clone, PartialEq, Eq)] +struct RootNodePair { + // The implicit "viewport" node created by Bevy + implicit_viewport_node: taffy::node::Node, + // The root (parentless) node specified by the user + user_root_node: taffy::node::Node, +} + #[derive(Resource)] pub struct UiSurface { entity_to_taffy: HashMap, - window_nodes: HashMap, + window_roots: HashMap>, taffy: Taffy, } @@ -57,7 +65,7 @@ impl fmt::Debug for UiSurface { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("UiSurface") .field("entity_to_taffy", &self.entity_to_taffy) - .field("window_nodes", &self.window_nodes) + .field("window_nodes", &self.window_roots) .finish() } } @@ -68,7 +76,7 @@ impl Default for UiSurface { taffy.disable_rounding(); Self { entity_to_taffy: Default::default(), - window_nodes: Default::default(), + window_roots: Default::default(), taffy, } } @@ -132,50 +140,64 @@ without UI components as a child of an entity with UI components, results may be } } - /// Retrieve or insert the root layout node and update its size to match the size of the window. - pub fn update_window(&mut self, window: Entity, window_resolution: &WindowResolution) { - let taffy = &mut self.taffy; - let node = self - .window_nodes - .entry(window) - .or_insert_with(|| taffy.new_leaf(taffy::style::Style::default()).unwrap()); - - taffy - .set_style( - *node, - taffy::style::Style { - size: taffy::geometry::Size { - width: taffy::style::Dimension::Points( - window_resolution.physical_width() as f32 - ), - height: taffy::style::Dimension::Points( - window_resolution.physical_height() as f32, - ), - }, - ..Default::default() - }, - ) - .unwrap(); - } - /// Set the ui node entities without a [`Parent`] as children to the root node in the taffy layout. pub fn set_window_children( &mut self, - parent_window: Entity, + window_id: Entity, children: impl Iterator, ) { - let taffy_node = self.window_nodes.get(&parent_window).unwrap(); - let child_nodes = children - .map(|e| *self.entity_to_taffy.get(&e).unwrap()) - .collect::>(); - self.taffy.set_children(*taffy_node, &child_nodes).unwrap(); + let viewport_style = taffy::style::Style { + display: taffy::style::Display::Grid, + // 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), + }, + align_items: Some(taffy::style::AlignItems::Start), + justify_items: Some(taffy::style::JustifyItems::Start), + ..default() + }; + + let existing_roots = self.window_roots.entry(window_id).or_default(); + let mut new_roots = Vec::new(); + for entity in children { + let node = *self.entity_to_taffy.get(&entity).unwrap(); + let root_node = existing_roots + .iter() + .find(|n| n.user_root_node == node) + .cloned() + .unwrap_or_else(|| RootNodePair { + implicit_viewport_node: self + .taffy + .new_with_children(viewport_style.clone(), &[node]) + .unwrap(), + user_root_node: node, + }); + new_roots.push(root_node); + } + + // Cleanup the implicit root nodes of any user root nodes that have been removed + for old_root in existing_roots { + if !new_roots.contains(old_root) { + self.taffy.remove(old_root.implicit_viewport_node).unwrap(); + } + } + + self.window_roots.insert(window_id, new_roots); } /// Compute the layout for each window entity's corresponding root node in the layout. - pub fn compute_window_layouts(&mut self) { - for window_node in self.window_nodes.values() { + pub fn compute_window_layout(&mut self, window: Entity, window_resolution: &WindowResolution) { + let available_space = taffy::geometry::Size { + width: taffy::style::AvailableSpace::Definite(window_resolution.physical_width() as f32), + height: taffy::style::AvailableSpace::Definite( + window_resolution.physical_height() as f32 + ), + }; + for root_nodes in self.window_roots.entry(window).or_default() { self.taffy - .compute_layout(*window_node, Size::MAX_CONTENT) + .compute_layout(root_nodes.implicit_viewport_node, available_space) .unwrap(); } } @@ -251,11 +273,6 @@ pub fn ui_layout_system( .read() .any(|resized_window| resized_window.window == primary_window_entity); - // update window root nodes - for (entity, window) in windows.iter() { - ui_surface.update_window(entity, &window.resolution); - } - let scale_factor = logical_to_physical_factor * ui_scale.0; let layout_context = LayoutContext::new(scale_factor, physical_size); @@ -302,7 +319,9 @@ pub fn ui_layout_system( } // compute layouts - ui_surface.compute_window_layouts(); + for (entity, window) in windows.iter() { + ui_surface.compute_window_layout(entity, &window.resolution); + } let inverse_target_scale_factor = 1. / scale_factor; diff --git a/examples/ecs/apply_deferred.rs b/examples/ecs/apply_deferred.rs index 5942ca9262..fb0020e6f0 100644 --- a/examples/ecs/apply_deferred.rs +++ b/examples/ecs/apply_deferred.rs @@ -70,6 +70,7 @@ fn setup(mut commands: Commands) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, flex_direction: FlexDirection::Column, diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 31c0346206..c4603196f4 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -54,6 +54,7 @@ fn setup_menu(mut commands: Commands) { style: Style { // center button width: Val::Percent(100.), + height: Val::Percent(100.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index cbd300d83c..c6ee0a6e52 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -84,6 +84,7 @@ mod splash { align_items: AlignItems::Center, justify_content: JustifyContent::Center, width: Val::Percent(100.0), + height: Val::Percent(100.0), ..default() }, ..default() @@ -151,6 +152,7 @@ mod game { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), // center children align_items: AlignItems::Center, justify_content: JustifyContent::Center, @@ -421,6 +423,7 @@ mod menu { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() @@ -546,6 +549,7 @@ mod menu { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() @@ -611,6 +615,7 @@ mod menu { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() @@ -714,6 +719,7 @@ mod menu { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() diff --git a/examples/stress_tests/many_buttons.rs b/examples/stress_tests/many_buttons.rs index 1095076e16..d7c8533c37 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -120,6 +120,7 @@ fn setup_flex(mut commands: Commands, asset_server: Res, args: Res< justify_content: JustifyContent::Center, align_items: AlignItems::Center, width: Val::Percent(100.), + height: Val::Percent(100.), ..default() }, ..default() diff --git a/examples/stress_tests/many_glyphs.rs b/examples/stress_tests/many_glyphs.rs index 0b095c170b..398ac3cb64 100644 --- a/examples/stress_tests/many_glyphs.rs +++ b/examples/stress_tests/many_glyphs.rs @@ -54,7 +54,7 @@ fn setup(mut commands: Commands) { commands .spawn(NodeBundle { style: Style { - flex_basis: Val::Percent(100.), + width: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() diff --git a/examples/ui/borders.rs b/examples/ui/borders.rs index bf6cac544c..9e6cf4e2b3 100644 --- a/examples/ui/borders.rs +++ b/examples/ui/borders.rs @@ -14,8 +14,9 @@ fn setup(mut commands: Commands) { let root = commands .spawn(NodeBundle { style: Style { - flex_basis: Val::Percent(100.0), margin: UiRect::all(Val::Px(25.0)), + align_self: AlignSelf::Stretch, + justify_self: JustifySelf::Stretch, flex_wrap: FlexWrap::Wrap, justify_content: JustifyContent::FlexStart, align_items: AlignItems::FlexStart, diff --git a/examples/ui/button.rs b/examples/ui/button.rs index fef128f6f8..27ab4a19a1 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -58,6 +58,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() diff --git a/examples/ui/display_and_visibility.rs b/examples/ui/display_and_visibility.rs index 370ad677b8..55810a84b4 100644 --- a/examples/ui/display_and_visibility.rs +++ b/examples/ui/display_and_visibility.rs @@ -82,8 +82,9 @@ fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2dBundle::default()); commands.spawn(NodeBundle { style: Style { + width: Val::Percent(100.), + height: Val::Percent(100.), flex_direction: FlexDirection::Column, - flex_basis: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::SpaceEvenly, ..Default::default() @@ -189,9 +190,6 @@ fn spawn_left_panel(builder: &mut ChildBuilder, palette: &[Color; 4]) -> Vec) { commands .spawn(NodeBundle { style: Style { + width: Val::Percent(100.), + height: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::Center, - width: Val::Percent(100.), ..Default::default() }, background_color: Color::ANTIQUE_WHITE.into(), diff --git a/examples/ui/relative_cursor_position.rs b/examples/ui/relative_cursor_position.rs index 079637d8c4..84b9fa2fe1 100644 --- a/examples/ui/relative_cursor_position.rs +++ b/examples/ui/relative_cursor_position.rs @@ -19,6 +19,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, flex_direction: FlexDirection::Column, diff --git a/examples/ui/size_constraints.rs b/examples/ui/size_constraints.rs index 5f0892c14a..72daae989d 100644 --- a/examples/ui/size_constraints.rs +++ b/examples/ui/size_constraints.rs @@ -51,7 +51,8 @@ fn setup(mut commands: Commands, asset_server: Res) { commands .spawn(NodeBundle { style: Style { - flex_basis: Val::Percent(100.0), + width: Val::Percent(100.0), + height: Val::Percent(100.0), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..Default::default() diff --git a/examples/ui/text_wrap_debug.rs b/examples/ui/text_wrap_debug.rs index 374799ec81..a7d01e3ab8 100644 --- a/examples/ui/text_wrap_debug.rs +++ b/examples/ui/text_wrap_debug.rs @@ -24,8 +24,9 @@ fn spawn(mut commands: Commands, asset_server: Res) { let root = commands .spawn(NodeBundle { style: Style { - flex_direction: FlexDirection::Column, width: Val::Percent(100.), + height: Val::Percent(100.), + flex_direction: FlexDirection::Column, ..Default::default() }, background_color: Color::BLACK.into(), diff --git a/examples/ui/transparency_ui.rs b/examples/ui/transparency_ui.rs index 2a6fe7f9b9..2aabe43dc8 100644 --- a/examples/ui/transparency_ui.rs +++ b/examples/ui/transparency_ui.rs @@ -20,6 +20,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::SpaceAround, ..default() diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 2dea49e6ea..3eabcdd156 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -29,6 +29,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), justify_content: JustifyContent::SpaceBetween, ..default() }, diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index d6b16ce38a..8609288bf7 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -40,8 +40,9 @@ fn setup( commands .spawn(NodeBundle { style: Style { - flex_direction: FlexDirection::Column, width: Val::Percent(100.0), + height: Val::Percent(100.0), + flex_direction: FlexDirection::Column, justify_content: JustifyContent::Center, align_items: AlignItems::Center, row_gap: Val::Px(text_style.font_size * 2.), diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index f6eae5fe5c..0580391018 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -23,6 +23,7 @@ fn setup(mut commands: Commands) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.), + height: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() diff --git a/examples/window/scale_factor_override.rs b/examples/window/scale_factor_override.rs index a465432c00..bfa74cfe11 100644 --- a/examples/window/scale_factor_override.rs +++ b/examples/window/scale_factor_override.rs @@ -28,6 +28,7 @@ fn setup(mut commands: Commands) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), justify_content: JustifyContent::SpaceBetween, ..default() },