Skip to content

Commit

Permalink
Have a separate implicit viewport node per root node + make viewport …
Browse files Browse the repository at this point in the history
…node `Display::Grid` (bevyengine#9637)

# Objective

Make `bevy_ui` "root" nodes more intuitive to use/style by:
- Removing the implicit flexbox styling (such as stretch alignment) that
is applied to them, and replacing it with more intuitive CSS Grid
styling (notably with stretch alignment disabled in both axes).
- Making root nodes layout independently of each other. Instead of there
being a single implicit "viewport" node that all root nodes are children
of, there is now an implicit "viewport" node *per root node*. And layout
of each tree is computed separately.

## Solution

- Remove the global implicit viewport node, and instead create an
implicit viewport node for each user-specified root node.
- Keep track of both the user-specified root nodes and the implicit
viewport nodes in a separate `Vec`.
- Use the window's size as the `available_space` parameter to
`Taffy.compute_layout` rather than setting it on the implicit viewport
node (and set the viewport to `height: 100%; width: 100%` to make this
"just work").

---

## Changelog

- Bevy UI now lays out root nodes independently of each other in
separate layout contexts.
- The implicit viewport node (which contains each user-specified root
node) is now `Display::Grid` with `align_items` and `justify_items` both
set to `Start`.

## Migration Guide

- Bevy UI now lays out root nodes independently of each other in
separate layout contexts. If you were relying on your root nodes being
able to affect each other's layouts, then you may need to wrap them in a
single root node.
- The implicit viewport node (which contains each user-specified root
node) is now `Display::Grid` with `align_items` and `justify_items` both
set to `Start`. You may need to add `height: Val::Percent(100.)` to your
root nodes if you were previously relying on being implicitly set.
  • Loading branch information
nicoburns authored and Ray Redondo committed Jan 9, 2024
1 parent 1b72df3 commit 3c74219
Show file tree
Hide file tree
Showing 19 changed files with 105 additions and 66 deletions.
22 changes: 12 additions & 10 deletions crates/bevy_ui/src/layout/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
}
}
Expand Down
111 changes: 65 additions & 46 deletions crates/bevy_ui/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Entity, taffy::node::Node>,
window_nodes: HashMap<Entity, taffy::node::Node>,
window_roots: HashMap<Entity, Vec<RootNodePair>>,
taffy: Taffy,
}

Expand All @@ -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()
}
}
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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<Item = Entity>,
) {
let taffy_node = self.window_nodes.get(&parent_window).unwrap();
let child_nodes = children
.map(|e| *self.entity_to_taffy.get(&e).unwrap())
.collect::<Vec<taffy::node::Node>>();
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();
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions examples/ecs/apply_deferred.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions examples/ecs/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions examples/games/game_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions examples/stress_tests/many_buttons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ fn setup_flex(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
width: Val::Percent(100.),
height: Val::Percent(100.),
..default()
},
..default()
Expand Down
2 changes: 1 addition & 1 deletion examples/stress_tests/many_glyphs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion examples/ui/borders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions examples/ui/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
Expand Down
6 changes: 2 additions & 4 deletions examples/ui/display_and_visibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
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()
Expand Down Expand Up @@ -189,9 +190,6 @@ fn spawn_left_panel(builder: &mut ChildBuilder, palette: &[Color; 4]) -> Vec<Ent
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
..Default::default()
},
background_color: BackgroundColor(Color::BLACK),
..Default::default()
})
Expand Down
3 changes: 2 additions & 1 deletion examples/ui/overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
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(),
Expand Down
1 change: 1 addition & 0 deletions examples/ui/relative_cursor_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.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,
Expand Down
3 changes: 2 additions & 1 deletion examples/ui/size_constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
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()
Expand Down
3 changes: 2 additions & 1 deletion examples/ui/text_wrap_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
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(),
Expand Down
1 change: 1 addition & 0 deletions examples/ui/transparency_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::SpaceAround,
..default()
Expand Down
1 change: 1 addition & 0 deletions examples/ui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
justify_content: JustifyContent::SpaceBetween,
..default()
},
Expand Down
3 changes: 2 additions & 1 deletion examples/ui/ui_texture_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.),
Expand Down
Loading

0 comments on commit 3c74219

Please sign in to comment.