From f27b275a54ddf361a0452bfcea7224195994ebc4 Mon Sep 17 00:00:00 2001 From: Brett Striker Date: Mon, 1 Apr 2024 18:47:34 -0400 Subject: [PATCH] [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 --- crates/bevy_ui/src/layout/debug.rs | 9 +- crates/bevy_ui/src/layout/mod.rs | 248 ++---------------------- crates/bevy_ui/src/layout/ui_surface.rs | 226 +++++++++++++++++++++ crates/bevy_ui/src/lib.rs | 1 + 4 files changed, 253 insertions(+), 231 deletions(-) create mode 100644 crates/bevy_ui/src/layout/ui_surface.rs diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index 8d5f27e169eca..67e7a205b2915 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -1,10 +1,13 @@ -use crate::UiSurface; -use bevy_ecs::prelude::Entity; -use bevy_utils::HashMap; use std::fmt::Write; + use taffy::prelude::Node; use taffy::tree::LayoutTree; +use bevy_ecs::prelude::Entity; +use bevy_utils::HashMap; + +use crate::layout::ui_surface::UiSurface; + /// Prints a debug representation of the computed layout of the UI layout tree for each window. pub fn print_ui_layout_tree(ui_surface: &UiSurface) { let taffy_to_entity: HashMap = ui_surface diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 7ff8a59229fca..62d60f50cbb18 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1,14 +1,12 @@ -mod convert; -pub mod debug; +use thiserror::Error; -use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale}; use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, - entity::{Entity, EntityHashMap}, + entity::Entity, event::EventReader, query::{With, Without}, removal_detection::RemovedComponents, - system::{Query, Res, ResMut, Resource, SystemParam}, + system::{Query, Res, ResMut, SystemParam}, world::Ref, }; use bevy_hierarchy::{Children, Parent}; @@ -16,11 +14,15 @@ use bevy_math::{UVec2, Vec2}; use bevy_render::camera::{Camera, NormalizedRenderTarget}; use bevy_transform::components::Transform; use bevy_utils::tracing::warn; -use bevy_utils::{default, HashMap, HashSet}; +use bevy_utils::{HashMap, HashSet}; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; -use std::fmt; -use taffy::{tree::LayoutTree, Taffy}; -use thiserror::Error; +use ui_surface::UiSurface; + +use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale}; + +mod convert; +pub mod debug; +pub(crate) mod ui_surface; pub struct LayoutContext { pub scale_factor: f32, @@ -41,218 +43,6 @@ 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: EntityHashMap, - camera_entity_to_taffy: EntityHashMap>, - camera_roots: EntityHashMap>, - taffy: Taffy, -} - -fn _assert_send_sync_ui_surface_impl_safe() { - fn _assert_send_sync() {} - _assert_send_sync::>(); - _assert_send_sync::(); - _assert_send_sync::(); -} - -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("camera_roots", &self.camera_roots) - .finish() - } -} - -impl Default for UiSurface { - fn default() -> Self { - let mut taffy = Taffy::new(); - taffy.disable_rounding(); - Self { - entity_to_taffy: Default::default(), - camera_entity_to_taffy: Default::default(), - camera_roots: Default::default(), - taffy, - } - } -} - -impl UiSurface { - /// Retrieves the Taffy node associated with the given UI node entity and updates its style. - /// If no associated Taffy node exists a new Taffy node is inserted into the Taffy layout. - pub fn upsert_node(&mut self, entity: Entity, style: &Style, context: &LayoutContext) { - let mut added = false; - let taffy = &mut self.taffy; - let taffy_node = self.entity_to_taffy.entry(entity).or_insert_with(|| { - added = true; - taffy.new_leaf(convert::from_style(context, style)).unwrap() - }); - - if !added { - self.taffy - .set_style(*taffy_node, convert::from_style(context, style)) - .unwrap(); - } - } - - /// Update the `MeasureFunc` of the taffy node corresponding to the given [`Entity`] if the node exists. - pub fn try_update_measure( - &mut self, - entity: Entity, - measure_func: taffy::node::MeasureFunc, - ) -> Option<()> { - let taffy_node = self.entity_to_taffy.get(&entity)?; - - self.taffy.set_measure(*taffy_node, Some(measure_func)).ok() - } - - /// Update the children of the taffy node corresponding to the given [`Entity`]. - pub fn update_children(&mut self, entity: Entity, children: &Children) { - let mut taffy_children = Vec::with_capacity(children.len()); - for child in children { - if let Some(taffy_node) = self.entity_to_taffy.get(child) { - taffy_children.push(*taffy_node); - } else { - warn!( - "Unstyled child in a UI entity hierarchy. You are using an entity \ -without UI components as a child of an entity with UI components, results may be unexpected." - ); - } - } - - let taffy_node = self.entity_to_taffy.get(&entity).unwrap(); - self.taffy - .set_children(*taffy_node, &taffy_children) - .unwrap(); - } - - /// Removes children from the entity's taffy node if it exists. Does nothing otherwise. - pub fn try_remove_children(&mut self, entity: Entity) { - if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { - self.taffy.set_children(*taffy_node, &[]).unwrap(); - } - } - - /// Removes the measure from the entity's taffy node if it exists. Does nothing otherwise. - pub fn try_remove_measure(&mut self, entity: Entity) { - if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { - self.taffy.set_measure(*taffy_node, None).unwrap(); - } - } - - /// Set the ui node entities without a [`Parent`] as children to the root node in the taffy layout. - pub fn set_camera_children( - &mut self, - camera_id: Entity, - children: impl Iterator, - ) { - 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 camera_root_node_map = self.camera_entity_to_taffy.entry(camera_id).or_default(); - let existing_roots = self.camera_roots.entry(camera_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(|| { - if let Some(previous_parent) = self.taffy.parent(node) { - // remove the root node from the previous implicit node's children - self.taffy.remove_child(previous_parent, node).unwrap(); - } - - let viewport_node = *camera_root_node_map - .entry(entity) - .or_insert_with(|| self.taffy.new_leaf(viewport_style.clone()).unwrap()); - self.taffy.add_child(viewport_node, node).unwrap(); - - RootNodePair { - implicit_viewport_node: viewport_node, - user_root_node: node, - } - }); - new_roots.push(root_node); - } - - self.camera_roots.insert(camera_id, new_roots); - } - - /// Compute the layout for each window entity's corresponding root node in the layout. - pub fn compute_camera_layout(&mut self, camera: Entity, render_target_resolution: UVec2) { - let Some(camera_root_nodes) = self.camera_roots.get(&camera) else { - return; - }; - - let available_space = taffy::geometry::Size { - width: taffy::style::AvailableSpace::Definite(render_target_resolution.x as f32), - height: taffy::style::AvailableSpace::Definite(render_target_resolution.y as f32), - }; - for root_nodes in camera_root_nodes { - self.taffy - .compute_layout(root_nodes.implicit_viewport_node, available_space) - .unwrap(); - } - } - - /// Removes each camera entity from the internal map and then removes their associated node from taffy - pub fn remove_camera_entities(&mut self, entities: impl IntoIterator) { - for entity in entities { - if let Some(camera_root_node_map) = self.camera_entity_to_taffy.remove(&entity) { - for (_, node) in camera_root_node_map.iter() { - self.taffy.remove(*node).unwrap(); - } - } - } - } - - /// Removes each entity from the internal map and then removes their associated node from taffy - pub fn remove_entities(&mut self, entities: impl IntoIterator) { - for entity in entities { - if let Some(node) = self.entity_to_taffy.remove(&entity) { - self.taffy.remove(node).unwrap(); - } - } - } - - /// Get the layout geometry for the taffy node corresponding to the ui node [`Entity`]. - /// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function. - pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, LayoutError> { - if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { - self.taffy - .layout(*taffy_node) - .map_err(LayoutError::TaffyError) - } else { - warn!( - "Styled child in a non-UI entity hierarchy. You are using an entity \ -with UI components as a child of an entity without UI components, results may be unexpected." - ); - Err(LayoutError::InvalidHierarchy) - } - } -} - #[derive(Debug, Error)] pub enum LayoutError { #[error("Invalid hierarchy")] @@ -533,12 +323,8 @@ fn round_layout_coords(value: Vec2) -> Vec2 { #[cfg(test)] mod tests { - use crate::layout::round_layout_coords; - use crate::prelude::*; - use crate::ui_layout_system; - use crate::update::update_target_camera_system; - use crate::ContentSize; - use crate::UiSurface; + use taffy::tree::LayoutTree; + use bevy_asset::AssetEvent; use bevy_asset::Assets; use bevy_core_pipeline::core_2d::Camera2dBundle; @@ -566,7 +352,13 @@ mod tests { use bevy_window::WindowResized; use bevy_window::WindowResolution; use bevy_window::WindowScaleFactorChanged; - use taffy::tree::LayoutTree; + + use crate::layout::round_layout_coords; + use crate::layout::ui_surface::UiSurface; + use crate::prelude::*; + use crate::ui_layout_system; + use crate::update::update_target_camera_system; + use crate::ContentSize; #[test] fn round_layout_coords_must_round_ties_up() { diff --git a/crates/bevy_ui/src/layout/ui_surface.rs b/crates/bevy_ui/src/layout/ui_surface.rs new file mode 100644 index 0000000000000..74586e8786ac0 --- /dev/null +++ b/crates/bevy_ui/src/layout/ui_surface.rs @@ -0,0 +1,226 @@ +use std::fmt; + +use taffy::prelude::LayoutTree; +use taffy::Taffy; + +use bevy_ecs::entity::{Entity, EntityHashMap}; +use bevy_ecs::prelude::Resource; +use bevy_hierarchy::Children; +use bevy_math::UVec2; +use bevy_utils::default; +use bevy_utils::tracing::warn; + +use crate::layout::convert; +use crate::{LayoutContext, LayoutError, Style}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RootNodePair { + // The implicit "viewport" node created by Bevy + pub(super) implicit_viewport_node: taffy::node::Node, + // The root (parentless) node specified by the user + pub(super) user_root_node: taffy::node::Node, +} + +#[derive(Resource)] +pub struct UiSurface { + pub(super) entity_to_taffy: EntityHashMap, + pub(super) camera_entity_to_taffy: EntityHashMap>, + pub(super) camera_roots: EntityHashMap>, + pub(super) taffy: Taffy, +} + +fn _assert_send_sync_ui_surface_impl_safe() { + fn _assert_send_sync() {} + _assert_send_sync::>(); + _assert_send_sync::(); + _assert_send_sync::(); +} + +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("camera_roots", &self.camera_roots) + .finish() + } +} + +impl Default for UiSurface { + fn default() -> Self { + let mut taffy = Taffy::new(); + taffy.disable_rounding(); + Self { + entity_to_taffy: Default::default(), + camera_entity_to_taffy: Default::default(), + camera_roots: Default::default(), + taffy, + } + } +} + +impl UiSurface { + /// Retrieves the Taffy node associated with the given UI node entity and updates its style. + /// If no associated Taffy node exists a new Taffy node is inserted into the Taffy layout. + pub fn upsert_node(&mut self, entity: Entity, style: &Style, context: &LayoutContext) { + let mut added = false; + let taffy = &mut self.taffy; + let taffy_node = self.entity_to_taffy.entry(entity).or_insert_with(|| { + added = true; + taffy.new_leaf(convert::from_style(context, style)).unwrap() + }); + + if !added { + self.taffy + .set_style(*taffy_node, convert::from_style(context, style)) + .unwrap(); + } + } + + /// Update the `MeasureFunc` of the taffy node corresponding to the given [`Entity`] if the node exists. + pub fn try_update_measure( + &mut self, + entity: Entity, + measure_func: taffy::node::MeasureFunc, + ) -> Option<()> { + let taffy_node = self.entity_to_taffy.get(&entity)?; + + self.taffy.set_measure(*taffy_node, Some(measure_func)).ok() + } + + /// Update the children of the taffy node corresponding to the given [`Entity`]. + pub fn update_children(&mut self, entity: Entity, children: &Children) { + let mut taffy_children = Vec::with_capacity(children.len()); + for child in children { + if let Some(taffy_node) = self.entity_to_taffy.get(child) { + taffy_children.push(*taffy_node); + } else { + warn!( + "Unstyled child in a UI entity hierarchy. You are using an entity \ +without UI components as a child of an entity with UI components, results may be unexpected." + ); + } + } + + let taffy_node = self.entity_to_taffy.get(&entity).unwrap(); + self.taffy + .set_children(*taffy_node, &taffy_children) + .unwrap(); + } + + /// Removes children from the entity's taffy node if it exists. Does nothing otherwise. + pub fn try_remove_children(&mut self, entity: Entity) { + if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { + self.taffy.set_children(*taffy_node, &[]).unwrap(); + } + } + + /// Removes the measure from the entity's taffy node if it exists. Does nothing otherwise. + pub fn try_remove_measure(&mut self, entity: Entity) { + if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { + self.taffy.set_measure(*taffy_node, None).unwrap(); + } + } + + /// Set the ui node entities without a [`bevy_hierarchy::Parent`] as children to the root node in the taffy layout. + pub fn set_camera_children( + &mut self, + camera_id: Entity, + children: impl Iterator, + ) { + 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 camera_root_node_map = self.camera_entity_to_taffy.entry(camera_id).or_default(); + let existing_roots = self.camera_roots.entry(camera_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(|| { + if let Some(previous_parent) = self.taffy.parent(node) { + // remove the root node from the previous implicit node's children + self.taffy.remove_child(previous_parent, node).unwrap(); + } + + let viewport_node = *camera_root_node_map + .entry(entity) + .or_insert_with(|| self.taffy.new_leaf(viewport_style.clone()).unwrap()); + self.taffy.add_child(viewport_node, node).unwrap(); + + RootNodePair { + implicit_viewport_node: viewport_node, + user_root_node: node, + } + }); + new_roots.push(root_node); + } + + self.camera_roots.insert(camera_id, new_roots); + } + + /// Compute the layout for each window entity's corresponding root node in the layout. + pub fn compute_camera_layout(&mut self, camera: Entity, render_target_resolution: UVec2) { + let Some(camera_root_nodes) = self.camera_roots.get(&camera) else { + return; + }; + + let available_space = taffy::geometry::Size { + width: taffy::style::AvailableSpace::Definite(render_target_resolution.x as f32), + height: taffy::style::AvailableSpace::Definite(render_target_resolution.y as f32), + }; + for root_nodes in camera_root_nodes { + self.taffy + .compute_layout(root_nodes.implicit_viewport_node, available_space) + .unwrap(); + } + } + + /// Removes each camera entity from the internal map and then removes their associated node from taffy + pub fn remove_camera_entities(&mut self, entities: impl IntoIterator) { + for entity in entities { + if let Some(camera_root_node_map) = self.camera_entity_to_taffy.remove(&entity) { + for (_, node) in camera_root_node_map.iter() { + self.taffy.remove(*node).unwrap(); + } + } + } + } + + /// Removes each entity from the internal map and then removes their associated node from taffy + pub fn remove_entities(&mut self, entities: impl IntoIterator) { + for entity in entities { + if let Some(node) = self.entity_to_taffy.remove(&entity) { + self.taffy.remove(node).unwrap(); + } + } + } + + /// Get the layout geometry for the taffy node corresponding to the ui node [`Entity`]. + /// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function. + pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, LayoutError> { + if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { + self.taffy + .layout(*taffy_node) + .map_err(LayoutError::TaffyError) + } else { + warn!( + "Styled child in a non-UI entity hierarchy. You are using an entity \ +with UI components as a child of an entity without UI components, results may be unexpected." + ); + Err(LayoutError::InvalidHierarchy) + } + } +} diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 32cefb8299c8e..e1eacd8d559a9 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -55,6 +55,7 @@ use bevy_ecs::prelude::*; use bevy_input::InputSystem; use bevy_render::RenderApp; use bevy_transform::TransformSystem; +use layout::ui_surface::UiSurface; use stack::ui_stack_system; pub use stack::UiStack; use update::{update_clipping_system, update_target_camera_system};