mod convert; use crate::{CalculatedSize, Node, Style, UiScale}; use bevy_ecs::{ change_detection::DetectChanges, entity::Entity, event::EventReader, query::{Changed, ReadOnlyWorldQuery, With, Without}, removal_detection::RemovedComponents, system::{Query, 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::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged}; use std::fmt; use taffy::{ prelude::{AvailableSpace, Size}, style_helpers::TaffyMaxContent, Taffy, }; pub struct LayoutContext { pub scale_factor: f64, pub physical_size: Vec2, pub min_size: f32, pub max_size: f32, } impl LayoutContext { /// create new a [`LayoutContext`] from the window's physical size and scale factor fn new(scale_factor: f64, physical_size: Vec2) -> Self { Self { scale_factor, physical_size, min_size: physical_size.x.min(physical_size.y), max_size: physical_size.x.max(physical_size.y), } } } #[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 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::(); } 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, 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(); } } pub fn upsert_leaf( &mut self, entity: Entity, style: &Style, calculated_size: CalculatedSize, context: &LayoutContext, ) { let taffy = &mut self.taffy; let taffy_style = convert::from_style(context, style); let scale_factor = context.scale_factor; let measure = taffy::node::MeasureFunc::Boxed(Box::new( move |constraints: Size>, _available: Size| { let mut size = Size { width: (scale_factor * calculated_size.size.x as f64) as f32, height: (scale_factor * calculated_size.size.y as f64) as f32, }; 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_with_measure(taffy_style, measure).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(); } } /// 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(); } } pub fn update_window(&mut self, window: Entity, window_resolution: &WindowResolution) { let taffy = &mut self.taffy; let node = self .window_nodes .entry(window) .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_resolution.physical_width() as f32 ), height: taffy::style::Dimension::Points( window_resolution.physical_height() as f32, ), }, ..Default::default() }, ) .unwrap(); } pub fn set_window_children( &mut self, parent_window: Entity, 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(); } 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( primary_window: Query<(Entity, &Window), With>, windows: Query<(Entity, &Window)>, ui_scale: Res, mut scale_factor_events: EventReader, mut resize_events: EventReader, mut flex_surface: ResMut, root_node_query: Query, Without)>, node_query: Query<(Entity, &Style, Option<&CalculatedSize>), (With, Changed