diff --git a/crates/bevy_ui/src/layout/convert.rs b/crates/bevy_ui/src/layout/convert.rs index d3fe37679849d..57e2e3b2dab88 100644 --- a/crates/bevy_ui/src/layout/convert.rs +++ b/crates/bevy_ui/src/layout/convert.rs @@ -63,7 +63,7 @@ impl UiRect { } } -pub fn from_style(context: &LayoutContext, style: &Style) -> taffy::style::Style { +pub fn from_style(style: &Style, context: &LayoutContext) -> taffy::style::Style { taffy::style::Style { display: style.display.into(), position: style.position_type.into(), @@ -466,8 +466,8 @@ mod tests { grid_column: GridPlacement::start(4), grid_row: GridPlacement::span(3), }; - let viewport_values = LayoutContext::new(1.0, bevy_math::Vec2::new(800., 600.)); - let taffy_style = from_style(&viewport_values, &bevy_style); + let context = LayoutContext::new(bevy_math::Vec2::new(800., 600.), 1.0); + let taffy_style = from_style(&bevy_style, &context); assert_eq!(taffy_style.display, taffy::style::Display::Flex); assert_eq!(taffy_style.position, taffy::style::Position::Absolute); assert!(matches!( @@ -633,7 +633,7 @@ mod tests { #[test] fn test_into_length_percentage() { use taffy::style::LengthPercentage; - let context = LayoutContext::new(2.0, bevy_math::Vec2::new(800., 600.)); + let context = LayoutContext::new(bevy_math::Vec2::new(800., 600.), 2.); let cases = [ (Val::Auto, LengthPercentage::Points(0.)), (Val::Percent(1.), LengthPercentage::Percent(0.01)), diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index cbe2c14f4fc81..a2a368c9b813c 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -6,19 +6,48 @@ use bevy_ecs::{ change_detection::DetectChanges, entity::Entity, event::EventReader, + prelude::Component, query::{With, Without}, + reflect::ReflectComponent, removal_detection::RemovedComponents, - system::{Query, Res, ResMut, Resource}, + system::{ParamSet, Query, Res, ResMut, Resource}, world::Ref, }; use bevy_hierarchy::{Children, Parent}; use bevy_log::warn; use bevy_math::Vec2; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::components::Transform; use bevy_utils::HashMap; use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged}; use std::fmt; -use taffy::{prelude::Size, style_helpers::TaffyMaxContent, Taffy}; +use taffy::{node::MeasureFunc, prelude::Size, style_helpers::TaffyMaxContent, Taffy}; + +type TaffyKey = taffy::node::Node; + +/// Identifier for the UI node's associated entry in the UI's layout tree. +/// +/// Users can only instantiate null nodes using `UiKey::default()`, the keys are set and managed internally by [`super::layout::ui_layout_system`]. +/// All UI nodes must have this component. +#[derive(Component, Debug, Default, Reflect)] +#[reflect(Component, Default)] +pub struct UiKey { + // The id of the taffy node associated with the entity possessing this component. + #[reflect(ignore)] + taffy_key: TaffyKey, +} + +impl UiKey { + // Returns the id of the taffy node associated with the entity that has this component. + pub fn get(&self) -> TaffyKey { + self.taffy_key + } + + // A null `UiKey` signifies that a UI node entity does not have an associated Taffy node in the UI layout tree. + pub fn is_null(&self) -> bool { + self.taffy_key == Default::default() + } +} pub struct LayoutContext { pub scale_factor: f64, @@ -29,7 +58,7 @@ pub struct LayoutContext { impl LayoutContext { /// create new a [`LayoutContext`] from the window's physical size and scale factor - fn new(scale_factor: f64, physical_size: Vec2) -> Self { + fn new(physical_size: Vec2, scale_factor: f64) -> Self { Self { scale_factor, physical_size, @@ -41,14 +70,14 @@ impl LayoutContext { #[derive(Resource)] pub struct UiSurface { - entity_to_taffy: HashMap, - window_nodes: HashMap, + entity_to_taffy: HashMap, + window_nodes: HashMap, taffy: Taffy, } fn _assert_send_sync_ui_surface_impl_safe() { fn _assert_send_sync() {} - _assert_send_sync::>(); + _assert_send_sync::>(); _assert_send_sync::(); _assert_send_sync::(); } @@ -75,35 +104,41 @@ impl Default for UiSurface { } 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() - }); + /// Returns a key identifying the internal layout tree node associated with the given `Entity` + /// or `None` if no such association exists. + #[inline] + pub fn get_key(&self, entity: Entity) -> Option { + self.entity_to_taffy.get(&entity).copied() + } - if !added { - self.taffy - .set_style(*taffy_node, convert::from_style(context, style)) - .unwrap(); - } + /// Inserts a node into the Taffy layout, associates it with the given entity, and returns its taffy id. + pub fn insert_node(&mut self, uinode: Entity) -> TaffyKey { + // It's possible for a user to overwrite an active `UiKey` component with `UiKey::default()`. + // In which case we reuse the existing taffy node. + *self + .entity_to_taffy + .entry(uinode) + .or_insert_with(|| self.taffy.new_leaf(taffy::style::Style::default()).unwrap()) + } + + /// Converts the given Bevy UI `Style` to a taffy style and applies it to the taffy node with identifier `key`. + pub fn set_style(&mut self, key: TaffyKey, style: &Style, layout_context: &LayoutContext) { + self.taffy + .set_style(key, convert::from_style(style, layout_context)) + .unwrap(); } - /// Update the `MeasureFunc` of the taffy node corresponding to the given [`Entity`]. - pub fn update_measure(&mut self, entity: Entity, measure_func: taffy::node::MeasureFunc) { - let taffy_node = self.entity_to_taffy.get(&entity).unwrap(); - self.taffy.set_measure(*taffy_node, Some(measure_func)).ok(); + /// Sets the `MeasureFunc` for the taffy node `key` to `measure`. + pub fn set_measure(&mut self, key: TaffyKey, measure: MeasureFunc) { + self.taffy.set_measure(key, Some(measure)).unwrap(); } - /// Update the children of the taffy node corresponding to the given [`Entity`]. - pub fn update_children(&mut self, entity: Entity, children: &Children) { + /// Update the children of the taffy node `parent_key` . + pub fn update_children(&mut self, parent_key: TaffyKey, 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); + for &child in children { + if let Some(child_key) = self.get_key(child) { + taffy_children.push(child_key); } else { warn!( "Unstyled child in a UI entity hierarchy. You are using an entity \ @@ -112,37 +147,36 @@ without UI components as a child of an entity with UI components, results may be } } - let taffy_node = self.entity_to_taffy.get(&entity).unwrap(); self.taffy - .set_children(*taffy_node, &taffy_children) + .set_children(parent_key, &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(); + pub fn try_remove_children(&mut self, parent: Entity) { + if let Some(key) = self.get_key(parent) { + self.taffy.set_children(key, &[]).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(); + if let Some(key) = self.get_key(entity) { + self.taffy.set_measure(key, None).unwrap(); } } /// 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 + let key = self .window_nodes .entry(window) .or_insert_with(|| taffy.new_leaf(taffy::style::Style::default()).unwrap()); taffy .set_style( - *node, + *key, taffy::style::Style { size: taffy::geometry::Size { width: taffy::style::Dimension::Points( @@ -162,20 +196,18 @@ without UI components as a child of an entity with UI components, results may be pub fn set_window_children( &mut self, parent_window: Entity, - children: impl Iterator, + children: impl Iterator, ) { - let taffy_node = self.window_nodes.get(&parent_window).unwrap(); - let child_nodes = children - .map(|e| *self.entity_to_taffy.get(&e).unwrap()) - .collect::>(); - self.taffy.set_children(*taffy_node, &child_nodes).unwrap(); + let window_key = self.window_nodes.get(&parent_window).unwrap(); + let child_keys = children.collect::>(); + self.taffy.set_children(*window_key, &child_keys).unwrap(); } /// 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() { + for window_key in self.window_nodes.values() { self.taffy - .compute_layout(*window_node, Size::MAX_CONTENT) + .compute_layout(*window_key, Size::MAX_CONTENT) .unwrap(); } } @@ -183,33 +215,11 @@ without UI components as a child of an entity with UI components, results may be /// 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(); + if let Some(key) = self.entity_to_taffy.remove(&entity) { + self.taffy.remove(key).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)] -pub enum LayoutError { - InvalidHierarchy, - TaffyError(taffy::error::TaffyError), } /// Updates the UI's layout tree, computes the new layout geometry and then updates the sizes and transforms of all the UI nodes. @@ -219,17 +229,24 @@ pub fn ui_layout_system( windows: Query<(Entity, &Window)>, ui_scale: Res, mut scale_factor_events: EventReader, - mut resize_events: EventReader, + mut window_resized_events: EventReader, + mut window_created_events: EventReader, mut ui_surface: ResMut, - root_node_query: Query, Without)>, - style_query: Query<(Entity, Ref