diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index 8d5f27e169eca..cede74519756e 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -1,31 +1,43 @@ -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 .entity_to_taffy .iter() - .map(|(entity, node)| (*node, *entity)) - .collect(); - for (&entity, roots) in &ui_surface.camera_roots { - let mut out = String::new(); - for root in roots { + .map(|(&ui_entity, &taffy_node)| (taffy_node, ui_entity)) + .collect::>(); + for (&camera_entity, root_node_set) in ui_surface.camera_root_nodes.iter() { + bevy_utils::tracing::info!("Layout tree for camera entity: {camera_entity}"); + for &root_node_entity in root_node_set.iter() { + let Some(implicit_viewport_node) = ui_surface + .root_node_data + .get(&root_node_entity) + .map(|rnd| rnd.implicit_viewport_node) + else { + continue; + }; + let mut out = String::new(); print_node( ui_surface, &taffy_to_entity, - entity, - root.implicit_viewport_node, + camera_entity, + implicit_viewport_node, false, String::new(), &mut out, ); + + bevy_utils::tracing::info!("{out}"); } - bevy_utils::tracing::info!("Layout tree for camera entity: {entity:?}\n{out}"); } } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 7ff8a59229fca..f82c6190c1e7d 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}, + query::{Added, 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")] @@ -283,6 +73,7 @@ pub fn ui_layout_system( style_query: Query<(Entity, Ref