Skip to content

Commit b995827

Browse files
authored
Have a separate implicit viewport node per root node + make viewport node Display::Grid (#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.
1 parent 401b2e7 commit b995827

19 files changed

+105
-66
lines changed

crates/bevy_ui/src/layout/debug.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@ pub fn print_ui_layout_tree(ui_surface: &UiSurface) {
1212
.iter()
1313
.map(|(entity, node)| (*node, *entity))
1414
.collect();
15-
for (&entity, &node) in &ui_surface.window_nodes {
15+
for (&entity, roots) in &ui_surface.window_roots {
1616
let mut out = String::new();
17-
print_node(
18-
ui_surface,
19-
&taffy_to_entity,
20-
entity,
21-
node,
22-
false,
23-
String::new(),
24-
&mut out,
25-
);
17+
for root in roots {
18+
print_node(
19+
ui_surface,
20+
&taffy_to_entity,
21+
entity,
22+
root.implicit_viewport_node,
23+
false,
24+
String::new(),
25+
&mut out,
26+
);
27+
}
2628
bevy_log::info!("Layout tree for window entity: {entity:?}\n{out}");
2729
}
2830
}

crates/bevy_ui/src/layout/mod.rs

Lines changed: 65 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ use bevy_hierarchy::{Children, Parent};
1515
use bevy_log::warn;
1616
use bevy_math::Vec2;
1717
use bevy_transform::components::Transform;
18-
use bevy_utils::HashMap;
18+
use bevy_utils::{default, HashMap};
1919
use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged};
2020
use std::fmt;
21-
use taffy::{prelude::Size, style_helpers::TaffyMaxContent, Taffy};
21+
use taffy::Taffy;
2222

2323
pub struct LayoutContext {
2424
pub scale_factor: f64,
@@ -39,10 +39,18 @@ impl LayoutContext {
3939
}
4040
}
4141

42+
#[derive(Debug, Clone, PartialEq, Eq)]
43+
struct RootNodePair {
44+
// The implicit "viewport" node created by Bevy
45+
implicit_viewport_node: taffy::node::Node,
46+
// The root (parentless) node specified by the user
47+
user_root_node: taffy::node::Node,
48+
}
49+
4250
#[derive(Resource)]
4351
pub struct UiSurface {
4452
entity_to_taffy: HashMap<Entity, taffy::node::Node>,
45-
window_nodes: HashMap<Entity, taffy::node::Node>,
53+
window_roots: HashMap<Entity, Vec<RootNodePair>>,
4654
taffy: Taffy,
4755
}
4856

@@ -57,7 +65,7 @@ impl fmt::Debug for UiSurface {
5765
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5866
f.debug_struct("UiSurface")
5967
.field("entity_to_taffy", &self.entity_to_taffy)
60-
.field("window_nodes", &self.window_nodes)
68+
.field("window_nodes", &self.window_roots)
6169
.finish()
6270
}
6371
}
@@ -68,7 +76,7 @@ impl Default for UiSurface {
6876
taffy.disable_rounding();
6977
Self {
7078
entity_to_taffy: Default::default(),
71-
window_nodes: Default::default(),
79+
window_roots: Default::default(),
7280
taffy,
7381
}
7482
}
@@ -132,50 +140,64 @@ without UI components as a child of an entity with UI components, results may be
132140
}
133141
}
134142

135-
/// Retrieve or insert the root layout node and update its size to match the size of the window.
136-
pub fn update_window(&mut self, window: Entity, window_resolution: &WindowResolution) {
137-
let taffy = &mut self.taffy;
138-
let node = self
139-
.window_nodes
140-
.entry(window)
141-
.or_insert_with(|| taffy.new_leaf(taffy::style::Style::default()).unwrap());
142-
143-
taffy
144-
.set_style(
145-
*node,
146-
taffy::style::Style {
147-
size: taffy::geometry::Size {
148-
width: taffy::style::Dimension::Points(
149-
window_resolution.physical_width() as f32
150-
),
151-
height: taffy::style::Dimension::Points(
152-
window_resolution.physical_height() as f32,
153-
),
154-
},
155-
..Default::default()
156-
},
157-
)
158-
.unwrap();
159-
}
160-
161143
/// Set the ui node entities without a [`Parent`] as children to the root node in the taffy layout.
162144
pub fn set_window_children(
163145
&mut self,
164-
parent_window: Entity,
146+
window_id: Entity,
165147
children: impl Iterator<Item = Entity>,
166148
) {
167-
let taffy_node = self.window_nodes.get(&parent_window).unwrap();
168-
let child_nodes = children
169-
.map(|e| *self.entity_to_taffy.get(&e).unwrap())
170-
.collect::<Vec<taffy::node::Node>>();
171-
self.taffy.set_children(*taffy_node, &child_nodes).unwrap();
149+
let viewport_style = taffy::style::Style {
150+
display: taffy::style::Display::Grid,
151+
// Note: Taffy percentages are floats ranging from 0.0 to 1.0.
152+
// So this is setting width:100% and height:100%
153+
size: taffy::geometry::Size {
154+
width: taffy::style::Dimension::Percent(1.0),
155+
height: taffy::style::Dimension::Percent(1.0),
156+
},
157+
align_items: Some(taffy::style::AlignItems::Start),
158+
justify_items: Some(taffy::style::JustifyItems::Start),
159+
..default()
160+
};
161+
162+
let existing_roots = self.window_roots.entry(window_id).or_default();
163+
let mut new_roots = Vec::new();
164+
for entity in children {
165+
let node = *self.entity_to_taffy.get(&entity).unwrap();
166+
let root_node = existing_roots
167+
.iter()
168+
.find(|n| n.user_root_node == node)
169+
.cloned()
170+
.unwrap_or_else(|| RootNodePair {
171+
implicit_viewport_node: self
172+
.taffy
173+
.new_with_children(viewport_style.clone(), &[node])
174+
.unwrap(),
175+
user_root_node: node,
176+
});
177+
new_roots.push(root_node);
178+
}
179+
180+
// Cleanup the implicit root nodes of any user root nodes that have been removed
181+
for old_root in existing_roots {
182+
if !new_roots.contains(old_root) {
183+
self.taffy.remove(old_root.implicit_viewport_node).unwrap();
184+
}
185+
}
186+
187+
self.window_roots.insert(window_id, new_roots);
172188
}
173189

174190
/// Compute the layout for each window entity's corresponding root node in the layout.
175-
pub fn compute_window_layouts(&mut self) {
176-
for window_node in self.window_nodes.values() {
191+
pub fn compute_window_layout(&mut self, window: Entity, window_resolution: &WindowResolution) {
192+
let available_space = taffy::geometry::Size {
193+
width: taffy::style::AvailableSpace::Definite(window_resolution.physical_width() as f32),
194+
height: taffy::style::AvailableSpace::Definite(
195+
window_resolution.physical_height() as f32
196+
),
197+
};
198+
for root_nodes in self.window_roots.entry(window).or_default() {
177199
self.taffy
178-
.compute_layout(*window_node, Size::MAX_CONTENT)
200+
.compute_layout(root_nodes.implicit_viewport_node, available_space)
179201
.unwrap();
180202
}
181203
}
@@ -251,11 +273,6 @@ pub fn ui_layout_system(
251273
.read()
252274
.any(|resized_window| resized_window.window == primary_window_entity);
253275

254-
// update window root nodes
255-
for (entity, window) in windows.iter() {
256-
ui_surface.update_window(entity, &window.resolution);
257-
}
258-
259276
let scale_factor = logical_to_physical_factor * ui_scale.0;
260277

261278
let layout_context = LayoutContext::new(scale_factor, physical_size);
@@ -302,7 +319,9 @@ pub fn ui_layout_system(
302319
}
303320

304321
// compute layouts
305-
ui_surface.compute_window_layouts();
322+
for (entity, window) in windows.iter() {
323+
ui_surface.compute_window_layout(entity, &window.resolution);
324+
}
306325

307326
let inverse_target_scale_factor = 1. / scale_factor;
308327

examples/ecs/apply_deferred.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ fn setup(mut commands: Commands) {
7070
.spawn(NodeBundle {
7171
style: Style {
7272
width: Val::Percent(100.0),
73+
height: Val::Percent(100.0),
7374
align_items: AlignItems::Center,
7475
justify_content: JustifyContent::Center,
7576
flex_direction: FlexDirection::Column,

examples/ecs/state.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fn setup_menu(mut commands: Commands) {
5454
style: Style {
5555
// center button
5656
width: Val::Percent(100.),
57+
height: Val::Percent(100.),
5758
justify_content: JustifyContent::Center,
5859
align_items: AlignItems::Center,
5960
..default()

examples/games/game_menu.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ mod splash {
8484
align_items: AlignItems::Center,
8585
justify_content: JustifyContent::Center,
8686
width: Val::Percent(100.0),
87+
height: Val::Percent(100.0),
8788
..default()
8889
},
8990
..default()
@@ -151,6 +152,7 @@ mod game {
151152
NodeBundle {
152153
style: Style {
153154
width: Val::Percent(100.0),
155+
height: Val::Percent(100.0),
154156
// center children
155157
align_items: AlignItems::Center,
156158
justify_content: JustifyContent::Center,
@@ -421,6 +423,7 @@ mod menu {
421423
NodeBundle {
422424
style: Style {
423425
width: Val::Percent(100.0),
426+
height: Val::Percent(100.0),
424427
align_items: AlignItems::Center,
425428
justify_content: JustifyContent::Center,
426429
..default()
@@ -546,6 +549,7 @@ mod menu {
546549
NodeBundle {
547550
style: Style {
548551
width: Val::Percent(100.0),
552+
height: Val::Percent(100.0),
549553
align_items: AlignItems::Center,
550554
justify_content: JustifyContent::Center,
551555
..default()
@@ -611,6 +615,7 @@ mod menu {
611615
NodeBundle {
612616
style: Style {
613617
width: Val::Percent(100.0),
618+
height: Val::Percent(100.0),
614619
align_items: AlignItems::Center,
615620
justify_content: JustifyContent::Center,
616621
..default()
@@ -714,6 +719,7 @@ mod menu {
714719
NodeBundle {
715720
style: Style {
716721
width: Val::Percent(100.0),
722+
height: Val::Percent(100.0),
717723
align_items: AlignItems::Center,
718724
justify_content: JustifyContent::Center,
719725
..default()

examples/stress_tests/many_buttons.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ fn setup_flex(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<
120120
justify_content: JustifyContent::Center,
121121
align_items: AlignItems::Center,
122122
width: Val::Percent(100.),
123+
height: Val::Percent(100.),
123124
..default()
124125
},
125126
..default()

examples/stress_tests/many_glyphs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn setup(mut commands: Commands) {
5454
commands
5555
.spawn(NodeBundle {
5656
style: Style {
57-
flex_basis: Val::Percent(100.),
57+
width: Val::Percent(100.),
5858
align_items: AlignItems::Center,
5959
justify_content: JustifyContent::Center,
6060
..default()

examples/ui/borders.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ fn setup(mut commands: Commands) {
1414
let root = commands
1515
.spawn(NodeBundle {
1616
style: Style {
17-
flex_basis: Val::Percent(100.0),
1817
margin: UiRect::all(Val::Px(25.0)),
18+
align_self: AlignSelf::Stretch,
19+
justify_self: JustifySelf::Stretch,
1920
flex_wrap: FlexWrap::Wrap,
2021
justify_content: JustifyContent::FlexStart,
2122
align_items: AlignItems::FlexStart,

examples/ui/button.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
5858
.spawn(NodeBundle {
5959
style: Style {
6060
width: Val::Percent(100.0),
61+
height: Val::Percent(100.0),
6162
align_items: AlignItems::Center,
6263
justify_content: JustifyContent::Center,
6364
..default()

examples/ui/display_and_visibility.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
8282
commands.spawn(Camera2dBundle::default());
8383
commands.spawn(NodeBundle {
8484
style: Style {
85+
width: Val::Percent(100.),
86+
height: Val::Percent(100.),
8587
flex_direction: FlexDirection::Column,
86-
flex_basis: Val::Percent(100.),
8788
align_items: AlignItems::Center,
8889
justify_content: JustifyContent::SpaceEvenly,
8990
..Default::default()
@@ -189,9 +190,6 @@ fn spawn_left_panel(builder: &mut ChildBuilder, palette: &[Color; 4]) -> Vec<Ent
189190
.with_children(|parent| {
190191
parent
191192
.spawn(NodeBundle {
192-
style: Style {
193-
..Default::default()
194-
},
195193
background_color: BackgroundColor(Color::BLACK),
196194
..Default::default()
197195
})

examples/ui/overflow.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
2525
commands
2626
.spawn(NodeBundle {
2727
style: Style {
28+
width: Val::Percent(100.),
29+
height: Val::Percent(100.),
2830
align_items: AlignItems::Center,
2931
justify_content: JustifyContent::Center,
30-
width: Val::Percent(100.),
3132
..Default::default()
3233
},
3334
background_color: Color::ANTIQUE_WHITE.into(),

examples/ui/relative_cursor_position.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
1919
.spawn(NodeBundle {
2020
style: Style {
2121
width: Val::Percent(100.),
22+
height: Val::Percent(100.0),
2223
align_items: AlignItems::Center,
2324
justify_content: JustifyContent::Center,
2425
flex_direction: FlexDirection::Column,

examples/ui/size_constraints.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
5151
commands
5252
.spawn(NodeBundle {
5353
style: Style {
54-
flex_basis: Val::Percent(100.0),
54+
width: Val::Percent(100.0),
55+
height: Val::Percent(100.0),
5556
justify_content: JustifyContent::Center,
5657
align_items: AlignItems::Center,
5758
..Default::default()

examples/ui/text_wrap_debug.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
2424
let root = commands
2525
.spawn(NodeBundle {
2626
style: Style {
27-
flex_direction: FlexDirection::Column,
2827
width: Val::Percent(100.),
28+
height: Val::Percent(100.),
29+
flex_direction: FlexDirection::Column,
2930
..Default::default()
3031
},
3132
background_color: Color::BLACK.into(),

examples/ui/transparency_ui.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
2020
.spawn(NodeBundle {
2121
style: Style {
2222
width: Val::Percent(100.0),
23+
height: Val::Percent(100.0),
2324
align_items: AlignItems::Center,
2425
justify_content: JustifyContent::SpaceAround,
2526
..default()

examples/ui/ui.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
2929
.spawn(NodeBundle {
3030
style: Style {
3131
width: Val::Percent(100.0),
32+
height: Val::Percent(100.0),
3233
justify_content: JustifyContent::SpaceBetween,
3334
..default()
3435
},

examples/ui/ui_texture_atlas.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ fn setup(
4040
commands
4141
.spawn(NodeBundle {
4242
style: Style {
43-
flex_direction: FlexDirection::Column,
4443
width: Val::Percent(100.0),
44+
height: Val::Percent(100.0),
45+
flex_direction: FlexDirection::Column,
4546
justify_content: JustifyContent::Center,
4647
align_items: AlignItems::Center,
4748
row_gap: Val::Px(text_style.font_size * 2.),

0 commit comments

Comments
 (0)