1use core::fmt;
2use core::ops::{Deref, DerefMut};
3
4use bevy_platform::collections::hash_map::Entry;
5use taffy::TaffyTree;
6
7use bevy_ecs::{
8 entity::{Entity, EntityHashMap},
9 prelude::Resource,
10};
11use bevy_math::{UVec2, Vec2};
12use bevy_utils::default;
13
14use crate::{layout::convert, LayoutContext, LayoutError, Measure, MeasureArgs, Node, NodeMeasure};
15use bevy_text::CosmicFontSystem;
16
17#[derive(Debug, Copy, Clone, PartialEq, Eq)]
18pub struct LayoutNode {
19 pub(super) viewport_id: Option<taffy::NodeId>,
21 pub(super) id: taffy::NodeId,
23}
24
25impl From<taffy::NodeId> for LayoutNode {
26 fn from(value: taffy::NodeId) -> Self {
27 LayoutNode {
28 viewport_id: None,
29 id: value,
30 }
31 }
32}
33
34pub(crate) struct UiTree<T>(TaffyTree<T>);
35
36#[expect(unsafe_code, reason = "TaffyTree is safe as long as calc is not used")]
37unsafe impl Send for UiTree<NodeMeasure> {}
39
40#[expect(unsafe_code, reason = "TaffyTree is safe as long as calc is not used")]
41unsafe impl Sync for UiTree<NodeMeasure> {}
43
44impl<T> Deref for UiTree<T> {
45 type Target = TaffyTree<T>;
46 fn deref(&self) -> &Self::Target {
47 &self.0
48 }
49}
50
51impl<T> DerefMut for UiTree<T> {
52 fn deref_mut(&mut self) -> &mut Self::Target {
53 &mut self.0
54 }
55}
56
57#[derive(Resource)]
58pub struct UiSurface {
59 pub root_entity_to_viewport_node: EntityHashMap<taffy::NodeId>,
60 pub(super) entity_to_taffy: EntityHashMap<LayoutNode>,
61 pub(super) taffy: UiTree<NodeMeasure>,
62 taffy_children_scratch: Vec<taffy::NodeId>,
63}
64
65fn _assert_send_sync_ui_surface_impl_safe() {
66 fn _assert_send_sync<T: Send + Sync>() {}
67 _assert_send_sync::<EntityHashMap<taffy::NodeId>>();
68 _assert_send_sync::<UiTree<NodeMeasure>>();
69 _assert_send_sync::<UiSurface>();
70}
71
72impl fmt::Debug for UiSurface {
73 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74 f.debug_struct("UiSurface")
75 .field("entity_to_taffy", &self.entity_to_taffy)
76 .field("taffy_children_scratch", &self.taffy_children_scratch)
77 .finish()
78 }
79}
80
81impl Default for UiSurface {
82 fn default() -> Self {
83 let taffy: UiTree<NodeMeasure> = UiTree(TaffyTree::new());
84 Self {
85 root_entity_to_viewport_node: Default::default(),
86 entity_to_taffy: Default::default(),
87 taffy,
88 taffy_children_scratch: Vec::new(),
89 }
90 }
91}
92
93impl UiSurface {
94 pub fn upsert_node(
97 &mut self,
98 layout_context: &LayoutContext,
99 entity: Entity,
100 node: &Node,
101 mut new_node_context: Option<NodeMeasure>,
102 ) {
103 let taffy = &mut self.taffy;
104
105 match self.entity_to_taffy.entry(entity) {
106 Entry::Occupied(entry) => {
107 let taffy_node = *entry.get();
108 let has_measure = if new_node_context.is_some() {
109 taffy
110 .set_node_context(taffy_node.id, new_node_context)
111 .unwrap();
112 true
113 } else {
114 taffy.get_node_context(taffy_node.id).is_some()
115 };
116
117 taffy
118 .set_style(
119 taffy_node.id,
120 convert::from_node(node, layout_context, has_measure),
121 )
122 .unwrap();
123 }
124 Entry::Vacant(entry) => {
125 let taffy_node = if let Some(measure) = new_node_context.take() {
126 taffy.new_leaf_with_context(
127 convert::from_node(node, layout_context, true),
128 measure,
129 )
130 } else {
131 taffy.new_leaf(convert::from_node(node, layout_context, false))
132 };
133 entry.insert(taffy_node.unwrap().into());
134 }
135 }
136 }
137
138 pub fn update_node_context(&mut self, entity: Entity, context: NodeMeasure) -> Option<()> {
140 let taffy_node = self.entity_to_taffy.get(&entity)?;
141 self.taffy
142 .set_node_context(taffy_node.id, Some(context))
143 .ok()
144 }
145
146 pub fn update_children(&mut self, entity: Entity, children: impl Iterator<Item = Entity>) {
148 self.taffy_children_scratch.clear();
149
150 for child in children {
151 if let Some(taffy_node) = self.entity_to_taffy.get_mut(&child) {
152 self.taffy_children_scratch.push(taffy_node.id);
153 if let Some(viewport_id) = taffy_node.viewport_id.take() {
154 self.taffy.remove(viewport_id).ok();
155 }
156 }
157 }
158
159 let taffy_node = self.entity_to_taffy.get(&entity).unwrap();
160 self.taffy
161 .set_children(taffy_node.id, &self.taffy_children_scratch)
162 .unwrap();
163 }
164
165 pub fn try_remove_children(&mut self, entity: Entity) {
167 if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
168 self.taffy.set_children(taffy_node.id, &[]).unwrap();
169 }
170 }
171
172 pub fn try_remove_node_context(&mut self, entity: Entity) {
174 if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
175 self.taffy.set_node_context(taffy_node.id, None).unwrap();
176 }
177 }
178
179 pub fn get_or_insert_taffy_viewport_node(&mut self, ui_root_entity: Entity) -> taffy::NodeId {
181 *self
182 .root_entity_to_viewport_node
183 .entry(ui_root_entity)
184 .or_insert_with(|| {
185 let root_node = self.entity_to_taffy.get_mut(&ui_root_entity).unwrap();
186 let implicit_root = self
187 .taffy
188 .new_leaf(taffy::style::Style {
189 display: taffy::style::Display::Grid,
190 size: taffy::geometry::Size {
193 width: taffy::style_helpers::percent(1.0),
194 height: taffy::style_helpers::percent(1.0),
195 },
196 align_items: Some(taffy::style::AlignItems::Start),
197 justify_items: Some(taffy::style::JustifyItems::Start),
198 ..default()
199 })
200 .unwrap();
201 self.taffy.add_child(implicit_root, root_node.id).unwrap();
202 root_node.viewport_id = Some(implicit_root);
203 implicit_root
204 })
205 }
206
207 pub fn compute_layout<'a>(
209 &mut self,
210 ui_root_entity: Entity,
211 render_target_resolution: UVec2,
212 buffer_query: &'a mut bevy_ecs::prelude::Query<&mut bevy_text::ComputedTextBlock>,
213 font_system: &'a mut CosmicFontSystem,
214 ) {
215 let implicit_viewport_node = self.get_or_insert_taffy_viewport_node(ui_root_entity);
216
217 let available_space = taffy::geometry::Size {
218 width: taffy::style::AvailableSpace::Definite(render_target_resolution.x as f32),
219 height: taffy::style::AvailableSpace::Definite(render_target_resolution.y as f32),
220 };
221
222 self.taffy
223 .compute_layout_with_measure(
224 implicit_viewport_node,
225 available_space,
226 |known_dimensions: taffy::Size<Option<f32>>,
227 available_space: taffy::Size<taffy::AvailableSpace>,
228 _node_id: taffy::NodeId,
229 context: Option<&mut NodeMeasure>,
230 style: &taffy::Style|
231 -> taffy::Size<f32> {
232 context
233 .map(|ctx| {
234 let buffer = get_text_buffer(
235 crate::widget::TextMeasure::needs_buffer(
236 known_dimensions.height,
237 available_space.width,
238 ),
239 ctx,
240 buffer_query,
241 );
242 let size = ctx.measure(
243 MeasureArgs {
244 width: known_dimensions.width,
245 height: known_dimensions.height,
246 available_width: available_space.width,
247 available_height: available_space.height,
248 font_system,
249 buffer,
250 },
251 style,
252 );
253 taffy::Size {
254 width: size.x,
255 height: size.y,
256 }
257 })
258 .unwrap_or(taffy::Size::ZERO)
259 },
260 )
261 .unwrap();
262 }
263
264 pub fn remove_entities(&mut self, entities: impl IntoIterator<Item = Entity>) {
266 for entity in entities {
267 if let Some(node) = self.entity_to_taffy.remove(&entity) {
268 self.taffy.remove(node.id).unwrap();
269 if let Some(viewport_node) = node.viewport_id {
270 self.taffy.remove(viewport_node).ok();
271 }
272 }
273 }
274 }
275
276 pub fn get_layout(
281 &mut self,
282 entity: Entity,
283 use_rounding: bool,
284 ) -> Result<(taffy::Layout, Vec2), LayoutError> {
285 let Some(taffy_node) = self.entity_to_taffy.get(&entity) else {
286 return Err(LayoutError::InvalidHierarchy);
287 };
288
289 if use_rounding {
290 self.taffy.enable_rounding();
291 } else {
292 self.taffy.disable_rounding();
293 }
294
295 let out = match self.taffy.layout(taffy_node.id).cloned() {
296 Ok(layout) => {
297 self.taffy.disable_rounding();
298 let taffy_size = self.taffy.layout(taffy_node.id).unwrap().size;
299 let unrounded_size = Vec2::new(taffy_size.width, taffy_size.height);
300 Ok((layout, unrounded_size))
301 }
302 Err(taffy_error) => Err(LayoutError::TaffyError(taffy_error)),
303 };
304
305 self.taffy.enable_rounding();
306 out
307 }
308}
309
310pub fn get_text_buffer<'a>(
311 needs_buffer: bool,
312 ctx: &mut NodeMeasure,
313 query: &'a mut bevy_ecs::prelude::Query<&mut bevy_text::ComputedTextBlock>,
314) -> Option<&'a mut bevy_text::ComputedTextBlock> {
315 if !needs_buffer {
317 return None;
318 }
319 let NodeMeasure::Text(crate::widget::TextMeasure { info }) = ctx else {
320 return None;
321 };
322 let Ok(computed) = query.get_mut(info.entity) else {
323 return None;
324 };
325 Some(computed.into_inner())
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331 use crate::{ContentSize, FixedMeasure};
332 use bevy_math::Vec2;
333 use taffy::TraversePartialTree;
334
335 #[test]
336 fn test_initialization() {
337 let ui_surface = UiSurface::default();
338 assert!(ui_surface.entity_to_taffy.is_empty());
339 assert_eq!(ui_surface.taffy.total_node_count(), 0);
340 }
341
342 #[test]
343 fn test_upsert() {
344 let mut ui_surface = UiSurface::default();
345 let root_node_entity = Entity::from_raw_u32(1).unwrap();
346 let node = Node::default();
347
348 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
350
351 assert_eq!(ui_surface.taffy.total_node_count(), 1);
353 assert!(ui_surface.entity_to_taffy.contains_key(&root_node_entity));
354
355 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
357
358 assert_eq!(ui_surface.taffy.total_node_count(), 1);
360
361 ui_surface.get_or_insert_taffy_viewport_node(root_node_entity);
363
364 assert_eq!(ui_surface.taffy.total_node_count(), 2);
366
367 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
369
370 assert_eq!(ui_surface.taffy.total_node_count(), 2);
372 }
373
374 #[test]
375 fn test_remove_entities() {
376 let mut ui_surface = UiSurface::default();
377 let root_node_entity = Entity::from_raw_u32(1).unwrap();
378 let node = Node::default();
379
380 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
381
382 ui_surface.get_or_insert_taffy_viewport_node(root_node_entity);
383
384 assert!(ui_surface.entity_to_taffy.contains_key(&root_node_entity));
385
386 ui_surface.remove_entities([root_node_entity]);
387 assert!(!ui_surface.entity_to_taffy.contains_key(&root_node_entity));
388 }
389
390 #[test]
391 fn test_try_update_measure() {
392 let mut ui_surface = UiSurface::default();
393 let root_node_entity = Entity::from_raw_u32(1).unwrap();
394 let node = Node::default();
395
396 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
397 let mut content_size = ContentSize::default();
398 content_size.set(NodeMeasure::Fixed(FixedMeasure { size: Vec2::ONE }));
399 let measure_func = content_size.measure.take().unwrap();
400 assert!(ui_surface
401 .update_node_context(root_node_entity, measure_func)
402 .is_some());
403 }
404
405 #[test]
406 fn test_update_children() {
407 let mut ui_surface = UiSurface::default();
408 let root_node_entity = Entity::from_raw_u32(1).unwrap();
409 let child_entity = Entity::from_raw_u32(2).unwrap();
410 let node = Node::default();
411
412 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
413 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, child_entity, &node, None);
414
415 ui_surface.update_children(root_node_entity, vec![child_entity].into_iter());
416
417 let parent_node = *ui_surface.entity_to_taffy.get(&root_node_entity).unwrap();
418 let child_node = *ui_surface.entity_to_taffy.get(&child_entity).unwrap();
419 assert_eq!(ui_surface.taffy.parent(child_node.id), Some(parent_node.id));
420 }
421
422 #[expect(
423 unreachable_code,
424 reason = "Certain pieces of code tested here cause the test to fail if made reachable; see #16362 for progress on fixing this"
425 )]
426 #[test]
427 fn test_set_camera_children() {
428 let mut ui_surface = UiSurface::default();
429 let root_node_entity = Entity::from_raw_u32(1).unwrap();
430 let child_entity = Entity::from_raw_u32(2).unwrap();
431 let node = Node::default();
432
433 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
434 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, child_entity, &node, None);
435
436 let root_taffy_node = *ui_surface.entity_to_taffy.get(&root_node_entity).unwrap();
437 let child_taffy = *ui_surface.entity_to_taffy.get(&child_entity).unwrap();
438
439 ui_surface
441 .taffy
442 .add_child(root_taffy_node.id, child_taffy.id)
443 .unwrap();
444
445 ui_surface.get_or_insert_taffy_viewport_node(root_node_entity);
446
447 assert_eq!(
448 ui_surface.taffy.parent(child_taffy.id),
449 Some(root_taffy_node.id)
450 );
451 let root_taffy_children = ui_surface.taffy.children(root_taffy_node.id).unwrap();
452 assert!(
453 root_taffy_children.contains(&child_taffy.id),
454 "root node is not a parent of child node"
455 );
456 assert_eq!(
457 ui_surface.taffy.child_count(root_taffy_node.id),
458 1,
459 "expected root node child count to be 1"
460 );
461
462 ui_surface.get_or_insert_taffy_viewport_node(root_node_entity);
464
465 return; let root_taffy_children = ui_surface.taffy.children(root_taffy_node.id).unwrap();
468 assert!(
469 root_taffy_children.contains(&child_taffy.id),
470 "root node is not a parent of child node"
471 );
472 assert_eq!(
473 ui_surface.taffy.child_count(root_taffy_node.id),
474 1,
475 "expected root node child count to be 1"
476 );
477
478 ui_surface.get_or_insert_taffy_viewport_node(root_node_entity);
480
481 let child_taffy = ui_surface.entity_to_taffy.get(&child_entity).unwrap();
482 let root_taffy_children = ui_surface.taffy.children(root_taffy_node.id).unwrap();
483 assert!(
484 root_taffy_children.contains(&child_taffy.id),
485 "root node is not a parent of child node"
486 );
487 assert_eq!(
488 ui_surface.taffy.child_count(root_taffy_node.id),
489 1,
490 "expected root node child count to be 1"
491 );
492 }
493}