mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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.
This commit is contained in:
parent
401b2e77f3
commit
b995827013
19 changed files with 105 additions and 66 deletions
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Entity, taffy::node::Node>,
|
||||
window_nodes: HashMap<Entity, taffy::node::Node>,
|
||||
window_roots: HashMap<Entity, Vec<RootNodePair>>,
|
||||
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<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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
@ -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()
|
||||
})
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
|
@ -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.),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue