mod convert; use crate::{CalculatedSize, Node, Style, UiScale}; use bevy_ecs::{ change_detection::DetectChanges, entity::Entity, event::EventReader, query::{Changed, ReadOnlyWorldQuery, With, Without}, system::{Query, RemovedComponents, Res, ResMut, Resource}, }; use bevy_hierarchy::{Children, Parent}; use bevy_log::warn; use bevy_math::Vec2; use bevy_transform::components::Transform; use bevy_utils::HashMap; use bevy_window::{Window, WindowId, WindowScaleFactorChanged, Windows}; use std::fmt; use taffy::{ prelude::{AvailableSpace, Size}, Taffy, }; #[derive(Resource)] pub struct FlexSurface { entity_to_taffy: HashMap, window_nodes: HashMap, taffy: Taffy, } // SAFETY: as long as MeasureFunc is Send + Sync. https://github.com/DioxusLabs/taffy/issues/146 // TODO: remove allow on lint - https://github.com/bevyengine/bevy/issues/3666 #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for FlexSurface {} unsafe impl Sync for FlexSurface {} fn _assert_send_sync_flex_surface_impl_safe() { fn _assert_send_sync() {} _assert_send_sync::>(); _assert_send_sync::>(); // FIXME https://github.com/DioxusLabs/taffy/issues/146 // _assert_send_sync::(); } impl fmt::Debug for FlexSurface { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("FlexSurface") .field("entity_to_taffy", &self.entity_to_taffy) .field("window_nodes", &self.window_nodes) .finish() } } impl Default for FlexSurface { fn default() -> Self { Self { entity_to_taffy: Default::default(), window_nodes: Default::default(), taffy: Taffy::new(), } } } impl FlexSurface { pub fn upsert_node(&mut self, entity: Entity, style: &Style, scale_factor: f64) { let mut added = false; let taffy = &mut self.taffy; let taffy_style = convert::from_style(scale_factor, style); let taffy_node = self.entity_to_taffy.entry(entity).or_insert_with(|| { added = true; taffy.new_leaf(taffy_style).unwrap() }); if !added { self.taffy.set_style(*taffy_node, taffy_style).unwrap(); } } pub fn upsert_leaf( &mut self, entity: Entity, style: &Style, calculated_size: CalculatedSize, scale_factor: f64, ) { let taffy = &mut self.taffy; let taffy_style = convert::from_style(scale_factor, style); let measure = taffy::node::MeasureFunc::Boxed(Box::new( move |constraints: Size>, _available: Size| { let mut size = convert::from_f32_size(scale_factor, calculated_size.size); match (constraints.width, constraints.height) { (None, None) => {} (Some(width), None) => { if calculated_size.preserve_aspect_ratio { size.height = width * size.height / size.width; } size.width = width; } (None, Some(height)) => { if calculated_size.preserve_aspect_ratio { size.width = height * size.width / size.height; } size.height = height; } (Some(width), Some(height)) => { size.width = width; size.height = height; } } size }, )); if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { self.taffy.set_style(*taffy_node, taffy_style).unwrap(); self.taffy.set_measure(*taffy_node, Some(measure)).unwrap(); } else { let taffy_node = taffy.new_leaf(taffy_style).unwrap(); self.entity_to_taffy.insert(entity, taffy_node); } } 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(); } } pub fn update_window(&mut self, window: &Window) { let taffy = &mut self.taffy; let node = self .window_nodes .entry(window.id()) .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.physical_width() as f32), height: taffy::style::Dimension::Points(window.physical_height() as f32), }, ..Default::default() }, ) .unwrap(); } pub fn set_window_children( &mut self, window_id: WindowId, children: impl Iterator, ) { let taffy_node = self.window_nodes.get(&window_id).unwrap(); let child_nodes = children .map(|e| *self.entity_to_taffy.get(&e).unwrap()) .collect::>(); self.taffy.set_children(*taffy_node, &child_nodes).unwrap(); } pub fn compute_window_layouts(&mut self) { for window_node in self.window_nodes.values() { self.taffy .compute_layout(*window_node, Size::MAX_CONTENT) .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(); } } } pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, FlexError> { if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { self.taffy .layout(*taffy_node) .map_err(FlexError::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(FlexError::InvalidHierarchy) } } } #[derive(Debug)] pub enum FlexError { InvalidHierarchy, TaffyError(taffy::error::TaffyError), } #[allow(clippy::too_many_arguments)] pub fn flex_node_system( windows: Res, ui_scale: Res, mut scale_factor_events: EventReader, mut flex_surface: ResMut, root_node_query: Query, Without)>, node_query: Query<(Entity, &Style, Option<&CalculatedSize>), (With, Changed