mod convert; use crate::{CalculatedSize, Node, Style}; use bevy_app::EventReader; use bevy_ecs::{ entity::Entity, query::{Changed, FilterFetch, With, Without, WorldQuery}, system::{Query, Res, ResMut}, }; use bevy_log::warn; use bevy_math::Vec2; use bevy_transform::prelude::{Children, Parent, Transform}; use bevy_utils::HashMap; use bevy_window::{Window, WindowId, WindowScaleFactorChanged, Windows}; use std::fmt; use stretch::{number::Number, Stretch}; pub struct FlexSurface { entity_to_stretch: HashMap, window_nodes: HashMap, stretch: Stretch, } // SAFE: as long as MeasureFunc is Send + Sync. https://github.com/vislyhq/stretch/issues/69 // 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/vislyhq/stretch/issues/69 // _assert_send_sync::(); } impl fmt::Debug for FlexSurface { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("FlexSurface") .field("entity_to_stretch", &self.entity_to_stretch) .field("window_nodes", &self.window_nodes) .finish() } } impl Default for FlexSurface { fn default() -> Self { Self { entity_to_stretch: Default::default(), window_nodes: Default::default(), stretch: Stretch::new(), } } } impl FlexSurface { pub fn upsert_node(&mut self, entity: Entity, style: &Style, scale_factor: f64) { let mut added = false; let stretch = &mut self.stretch; let stretch_style = convert::from_style(scale_factor, style); let stretch_node = self.entity_to_stretch.entry(entity).or_insert_with(|| { added = true; stretch.new_node(stretch_style, Vec::new()).unwrap() }); if !added { self.stretch .set_style(*stretch_node, stretch_style) .unwrap(); } } pub fn upsert_leaf( &mut self, entity: Entity, style: &Style, calculated_size: CalculatedSize, scale_factor: f64, ) { let stretch = &mut self.stretch; let stretch_style = convert::from_style(scale_factor, style); let measure = Box::new(move |constraints: stretch::geometry::Size| { let mut size = convert::from_f32_size(scale_factor, calculated_size.size); match (constraints.width, constraints.height) { (Number::Undefined, Number::Undefined) => {} (Number::Defined(width), Number::Undefined) => { size.height = width * size.height / size.width; size.width = width; } (Number::Undefined, Number::Defined(height)) => { size.width = height * size.width / size.height; size.height = height; } (Number::Defined(width), Number::Defined(height)) => { size.width = width; size.height = height; } } Ok(size) }); if let Some(stretch_node) = self.entity_to_stretch.get(&entity) { self.stretch .set_style(*stretch_node, stretch_style) .unwrap(); self.stretch .set_measure(*stretch_node, Some(measure)) .unwrap(); } else { let stretch_node = stretch.new_leaf(stretch_style, measure).unwrap(); self.entity_to_stretch.insert(entity, stretch_node); } } pub fn update_children(&mut self, entity: Entity, children: &Children) { let mut stretch_children = Vec::with_capacity(children.len()); for child in children.iter() { if let Some(stretch_node) = self.entity_to_stretch.get(child) { stretch_children.push(*stretch_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 stretch_node = self.entity_to_stretch.get(&entity).unwrap(); self.stretch .set_children(*stretch_node, stretch_children) .unwrap(); } pub fn update_window(&mut self, window: &Window) { let stretch = &mut self.stretch; let node = self.window_nodes.entry(window.id()).or_insert_with(|| { stretch .new_node(stretch::style::Style::default(), Vec::new()) .unwrap() }); stretch .set_style( *node, stretch::style::Style { size: stretch::geometry::Size { width: stretch::style::Dimension::Points(window.physical_width() as f32), height: stretch::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 stretch_node = self.window_nodes.get(&window_id).unwrap(); let child_nodes = children .map(|e| *self.entity_to_stretch.get(&e).unwrap()) .collect::>(); self.stretch .set_children(*stretch_node, child_nodes) .unwrap(); } pub fn compute_window_layouts(&mut self) { for window_node in self.window_nodes.values() { self.stretch .compute_layout(*window_node, stretch::geometry::Size::undefined()) .unwrap(); } } pub fn get_layout(&self, entity: Entity) -> Result<&stretch::result::Layout, FlexError> { if let Some(stretch_node) = self.entity_to_stretch.get(&entity) { self.stretch .layout(*stretch_node) .map_err(FlexError::StretchError) } 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, StretchError(stretch::Error), } #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub fn flex_node_system( windows: Res, mut scale_factor_events: EventReader, mut flex_surface: ResMut, root_node_query: Query, Without)>, node_query: Query<(Entity, &Style, Option<&CalculatedSize>), (With, Changed