Skip to content

Commit cf092d4

Browse files
[bevy_ui/layout] Extract UiSurface to its own file (#12801)
This is 1 of 5 iterative PR's that affect bevy_ui/layout --- # Objective - Extract `UiSurface` into its own file to make diffs in future PR's easier to digest ## Solution - Moved `UiSurface` to its own file
1 parent d0a5dda commit cf092d4

File tree

4 files changed

+253
-231
lines changed

4 files changed

+253
-231
lines changed

crates/bevy_ui/src/layout/debug.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
use crate::UiSurface;
2-
use bevy_ecs::prelude::Entity;
3-
use bevy_utils::HashMap;
41
use std::fmt::Write;
2+
53
use taffy::prelude::Node;
64
use taffy::tree::LayoutTree;
75

6+
use bevy_ecs::prelude::Entity;
7+
use bevy_utils::HashMap;
8+
9+
use crate::layout::ui_surface::UiSurface;
10+
811
/// Prints a debug representation of the computed layout of the UI layout tree for each window.
912
pub fn print_ui_layout_tree(ui_surface: &UiSurface) {
1013
let taffy_to_entity: HashMap<Node, Entity> = ui_surface

crates/bevy_ui/src/layout/mod.rs

Lines changed: 20 additions & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
mod convert;
2-
pub mod debug;
1+
use thiserror::Error;
32

4-
use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale};
53
use bevy_ecs::{
64
change_detection::{DetectChanges, DetectChangesMut},
7-
entity::{Entity, EntityHashMap},
5+
entity::Entity,
86
event::EventReader,
97
query::{With, Without},
108
removal_detection::RemovedComponents,
11-
system::{Query, Res, ResMut, Resource, SystemParam},
9+
system::{Query, Res, ResMut, SystemParam},
1210
world::Ref,
1311
};
1412
use bevy_hierarchy::{Children, Parent};
1513
use bevy_math::{UVec2, Vec2};
1614
use bevy_render::camera::{Camera, NormalizedRenderTarget};
1715
use bevy_transform::components::Transform;
1816
use bevy_utils::tracing::warn;
19-
use bevy_utils::{default, HashMap, HashSet};
17+
use bevy_utils::{HashMap, HashSet};
2018
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
21-
use std::fmt;
22-
use taffy::{tree::LayoutTree, Taffy};
23-
use thiserror::Error;
19+
use ui_surface::UiSurface;
20+
21+
use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale};
22+
23+
mod convert;
24+
pub mod debug;
25+
pub(crate) mod ui_surface;
2426

2527
pub struct LayoutContext {
2628
pub scale_factor: f32,
@@ -41,218 +43,6 @@ impl LayoutContext {
4143
}
4244
}
4345

44-
#[derive(Debug, Clone, PartialEq, Eq)]
45-
struct RootNodePair {
46-
// The implicit "viewport" node created by Bevy
47-
implicit_viewport_node: taffy::node::Node,
48-
// The root (parentless) node specified by the user
49-
user_root_node: taffy::node::Node,
50-
}
51-
52-
#[derive(Resource)]
53-
pub struct UiSurface {
54-
entity_to_taffy: EntityHashMap<taffy::node::Node>,
55-
camera_entity_to_taffy: EntityHashMap<EntityHashMap<taffy::node::Node>>,
56-
camera_roots: EntityHashMap<Vec<RootNodePair>>,
57-
taffy: Taffy,
58-
}
59-
60-
fn _assert_send_sync_ui_surface_impl_safe() {
61-
fn _assert_send_sync<T: Send + Sync>() {}
62-
_assert_send_sync::<EntityHashMap<taffy::node::Node>>();
63-
_assert_send_sync::<Taffy>();
64-
_assert_send_sync::<UiSurface>();
65-
}
66-
67-
impl fmt::Debug for UiSurface {
68-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69-
f.debug_struct("UiSurface")
70-
.field("entity_to_taffy", &self.entity_to_taffy)
71-
.field("camera_roots", &self.camera_roots)
72-
.finish()
73-
}
74-
}
75-
76-
impl Default for UiSurface {
77-
fn default() -> Self {
78-
let mut taffy = Taffy::new();
79-
taffy.disable_rounding();
80-
Self {
81-
entity_to_taffy: Default::default(),
82-
camera_entity_to_taffy: Default::default(),
83-
camera_roots: Default::default(),
84-
taffy,
85-
}
86-
}
87-
}
88-
89-
impl UiSurface {
90-
/// Retrieves the Taffy node associated with the given UI node entity and updates its style.
91-
/// If no associated Taffy node exists a new Taffy node is inserted into the Taffy layout.
92-
pub fn upsert_node(&mut self, entity: Entity, style: &Style, context: &LayoutContext) {
93-
let mut added = false;
94-
let taffy = &mut self.taffy;
95-
let taffy_node = self.entity_to_taffy.entry(entity).or_insert_with(|| {
96-
added = true;
97-
taffy.new_leaf(convert::from_style(context, style)).unwrap()
98-
});
99-
100-
if !added {
101-
self.taffy
102-
.set_style(*taffy_node, convert::from_style(context, style))
103-
.unwrap();
104-
}
105-
}
106-
107-
/// Update the `MeasureFunc` of the taffy node corresponding to the given [`Entity`] if the node exists.
108-
pub fn try_update_measure(
109-
&mut self,
110-
entity: Entity,
111-
measure_func: taffy::node::MeasureFunc,
112-
) -> Option<()> {
113-
let taffy_node = self.entity_to_taffy.get(&entity)?;
114-
115-
self.taffy.set_measure(*taffy_node, Some(measure_func)).ok()
116-
}
117-
118-
/// Update the children of the taffy node corresponding to the given [`Entity`].
119-
pub fn update_children(&mut self, entity: Entity, children: &Children) {
120-
let mut taffy_children = Vec::with_capacity(children.len());
121-
for child in children {
122-
if let Some(taffy_node) = self.entity_to_taffy.get(child) {
123-
taffy_children.push(*taffy_node);
124-
} else {
125-
warn!(
126-
"Unstyled child in a UI entity hierarchy. You are using an entity \
127-
without UI components as a child of an entity with UI components, results may be unexpected."
128-
);
129-
}
130-
}
131-
132-
let taffy_node = self.entity_to_taffy.get(&entity).unwrap();
133-
self.taffy
134-
.set_children(*taffy_node, &taffy_children)
135-
.unwrap();
136-
}
137-
138-
/// Removes children from the entity's taffy node if it exists. Does nothing otherwise.
139-
pub fn try_remove_children(&mut self, entity: Entity) {
140-
if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
141-
self.taffy.set_children(*taffy_node, &[]).unwrap();
142-
}
143-
}
144-
145-
/// Removes the measure from the entity's taffy node if it exists. Does nothing otherwise.
146-
pub fn try_remove_measure(&mut self, entity: Entity) {
147-
if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
148-
self.taffy.set_measure(*taffy_node, None).unwrap();
149-
}
150-
}
151-
152-
/// Set the ui node entities without a [`Parent`] as children to the root node in the taffy layout.
153-
pub fn set_camera_children(
154-
&mut self,
155-
camera_id: Entity,
156-
children: impl Iterator<Item = Entity>,
157-
) {
158-
let viewport_style = taffy::style::Style {
159-
display: taffy::style::Display::Grid,
160-
// Note: Taffy percentages are floats ranging from 0.0 to 1.0.
161-
// So this is setting width:100% and height:100%
162-
size: taffy::geometry::Size {
163-
width: taffy::style::Dimension::Percent(1.0),
164-
height: taffy::style::Dimension::Percent(1.0),
165-
},
166-
align_items: Some(taffy::style::AlignItems::Start),
167-
justify_items: Some(taffy::style::JustifyItems::Start),
168-
..default()
169-
};
170-
171-
let camera_root_node_map = self.camera_entity_to_taffy.entry(camera_id).or_default();
172-
let existing_roots = self.camera_roots.entry(camera_id).or_default();
173-
let mut new_roots = Vec::new();
174-
for entity in children {
175-
let node = *self.entity_to_taffy.get(&entity).unwrap();
176-
let root_node = existing_roots
177-
.iter()
178-
.find(|n| n.user_root_node == node)
179-
.cloned()
180-
.unwrap_or_else(|| {
181-
if let Some(previous_parent) = self.taffy.parent(node) {
182-
// remove the root node from the previous implicit node's children
183-
self.taffy.remove_child(previous_parent, node).unwrap();
184-
}
185-
186-
let viewport_node = *camera_root_node_map
187-
.entry(entity)
188-
.or_insert_with(|| self.taffy.new_leaf(viewport_style.clone()).unwrap());
189-
self.taffy.add_child(viewport_node, node).unwrap();
190-
191-
RootNodePair {
192-
implicit_viewport_node: viewport_node,
193-
user_root_node: node,
194-
}
195-
});
196-
new_roots.push(root_node);
197-
}
198-
199-
self.camera_roots.insert(camera_id, new_roots);
200-
}
201-
202-
/// Compute the layout for each window entity's corresponding root node in the layout.
203-
pub fn compute_camera_layout(&mut self, camera: Entity, render_target_resolution: UVec2) {
204-
let Some(camera_root_nodes) = self.camera_roots.get(&camera) else {
205-
return;
206-
};
207-
208-
let available_space = taffy::geometry::Size {
209-
width: taffy::style::AvailableSpace::Definite(render_target_resolution.x as f32),
210-
height: taffy::style::AvailableSpace::Definite(render_target_resolution.y as f32),
211-
};
212-
for root_nodes in camera_root_nodes {
213-
self.taffy
214-
.compute_layout(root_nodes.implicit_viewport_node, available_space)
215-
.unwrap();
216-
}
217-
}
218-
219-
/// Removes each camera entity from the internal map and then removes their associated node from taffy
220-
pub fn remove_camera_entities(&mut self, entities: impl IntoIterator<Item = Entity>) {
221-
for entity in entities {
222-
if let Some(camera_root_node_map) = self.camera_entity_to_taffy.remove(&entity) {
223-
for (_, node) in camera_root_node_map.iter() {
224-
self.taffy.remove(*node).unwrap();
225-
}
226-
}
227-
}
228-
}
229-
230-
/// Removes each entity from the internal map and then removes their associated node from taffy
231-
pub fn remove_entities(&mut self, entities: impl IntoIterator<Item = Entity>) {
232-
for entity in entities {
233-
if let Some(node) = self.entity_to_taffy.remove(&entity) {
234-
self.taffy.remove(node).unwrap();
235-
}
236-
}
237-
}
238-
239-
/// Get the layout geometry for the taffy node corresponding to the ui node [`Entity`].
240-
/// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function.
241-
pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, LayoutError> {
242-
if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
243-
self.taffy
244-
.layout(*taffy_node)
245-
.map_err(LayoutError::TaffyError)
246-
} else {
247-
warn!(
248-
"Styled child in a non-UI entity hierarchy. You are using an entity \
249-
with UI components as a child of an entity without UI components, results may be unexpected."
250-
);
251-
Err(LayoutError::InvalidHierarchy)
252-
}
253-
}
254-
}
255-
25646
#[derive(Debug, Error)]
25747
pub enum LayoutError {
25848
#[error("Invalid hierarchy")]
@@ -533,12 +323,8 @@ fn round_layout_coords(value: Vec2) -> Vec2 {
533323

534324
#[cfg(test)]
535325
mod tests {
536-
use crate::layout::round_layout_coords;
537-
use crate::prelude::*;
538-
use crate::ui_layout_system;
539-
use crate::update::update_target_camera_system;
540-
use crate::ContentSize;
541-
use crate::UiSurface;
326+
use taffy::tree::LayoutTree;
327+
542328
use bevy_asset::AssetEvent;
543329
use bevy_asset::Assets;
544330
use bevy_core_pipeline::core_2d::Camera2dBundle;
@@ -566,7 +352,13 @@ mod tests {
566352
use bevy_window::WindowResized;
567353
use bevy_window::WindowResolution;
568354
use bevy_window::WindowScaleFactorChanged;
569-
use taffy::tree::LayoutTree;
355+
356+
use crate::layout::round_layout_coords;
357+
use crate::layout::ui_surface::UiSurface;
358+
use crate::prelude::*;
359+
use crate::ui_layout_system;
360+
use crate::update::update_target_camera_system;
361+
use crate::ContentSize;
570362

571363
#[test]
572364
fn round_layout_coords_must_round_ties_up() {

0 commit comments

Comments
 (0)