1use crate::constraint_resolver::{ConstraintResolver, ResolveContext};
4use crate::dirty::{DirtyCounters, DirtyFlags, StyleGuard};
5use crate::metrics::{DirtyStats, MetricsTimer, UiMetrics};
6use crate::plugin::registry::WidgetTypeRegistry;
7use crate::style::Style;
8use crate::widgets::Widget;
9#[cfg(feature = "docking")]
10use crate::widgets::docking::{DockSplitter, DockTabs};
11use astrelis_core::alloc::HashSet;
12use astrelis_core::math::Vec2;
13use astrelis_core::profiling::{profile_function, profile_scope};
14use astrelis_text::FontRenderer;
15use astrelis_text::ShapedTextData;
16use indexmap::IndexMap;
17use std::sync::Arc;
18use taffy::{TaffyTree, prelude::*};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct NodeId(pub usize);
23
24#[derive(Debug, Clone, Copy)]
26pub struct LayoutRect {
27 pub x: f32,
28 pub y: f32,
29 pub width: f32,
30 pub height: f32,
31}
32
33impl LayoutRect {
34 pub fn contains(&self, point: Vec2) -> bool {
35 point.x >= self.x
36 && point.x <= self.x + self.width
37 && point.y >= self.y
38 && point.y <= self.y + self.height
39 }
40
41 pub fn position(&self) -> Vec2 {
42 Vec2::new(self.x, self.y)
43 }
44
45 pub fn size(&self) -> Vec2 {
46 Vec2::new(self.width, self.height)
47 }
48}
49
50#[cfg(feature = "docking")]
51enum DockingLayoutInfo {
53 Splitter {
54 children: Vec<NodeId>,
55 direction: crate::widgets::docking::SplitDirection,
56 split_ratio: f32,
57 separator_size: f32,
58 parent_layout: LayoutRect,
59 },
60 Tabs {
61 children: Vec<NodeId>,
62 active_tab: usize,
63 tab_bar_height: f32,
64 content_padding: f32,
65 parent_layout: LayoutRect,
66 },
67}
68
69pub struct UiNode {
71 pub widget: Box<dyn Widget>,
72 pub taffy_node: taffy::NodeId,
73 pub layout: LayoutRect,
74 pub dirty_flags: DirtyFlags,
75 pub parent: Option<NodeId>,
76 pub children: Vec<NodeId>,
77 pub text_measurement: Option<(f32, f32)>,
79 pub layout_version: u32,
81 pub text_version: u32,
82 pub paint_version: u32,
83 pub text_cache: Option<Arc<ShapedTextData>>,
85 pub computed_z_index: u16,
88 pub computed_opacity: f32,
91 pub computed_translate: Vec2,
94 pub computed_scale: Vec2,
97}
98
99impl UiNode {
100 pub fn bump_version(&mut self, flags: DirtyFlags) {
105 if flags.intersects(DirtyFlags::LAYOUT | DirtyFlags::CHILDREN_ORDER) {
106 self.layout_version = self.layout_version.wrapping_add(1);
107 }
108 if flags.contains(DirtyFlags::TEXT_SHAPING) {
109 self.text_version = self.text_version.wrapping_add(1);
110 self.text_cache = None;
112 }
113 if flags.intersects(DirtyFlags::COLOR | DirtyFlags::OPACITY | DirtyFlags::TRANSFORM) {
114 self.paint_version = self.paint_version.wrapping_add(1);
115 }
116 }
117}
118
119pub struct UiTree {
121 taffy: TaffyTree<()>,
122 nodes: IndexMap<NodeId, UiNode>,
123 root: Option<NodeId>,
124 next_id: usize,
125 dirty_nodes: HashSet<NodeId>,
127 dirty_roots: HashSet<NodeId>,
129 last_metrics: Option<UiMetrics>,
131 viewport_constraint_nodes: HashSet<NodeId>,
133 removed_nodes: Vec<NodeId>,
135 dirty_counters: DirtyCounters,
137 #[cfg(feature = "docking")]
139 docking_content_padding: f32,
140}
141
142impl UiTree {
143 pub fn new() -> Self {
145 Self {
146 taffy: TaffyTree::new(),
147 nodes: IndexMap::new(),
148 root: None,
149 next_id: 0,
150 dirty_nodes: HashSet::new(),
151 dirty_roots: HashSet::new(),
152 last_metrics: None,
153 viewport_constraint_nodes: HashSet::new(),
154 removed_nodes: Vec::new(),
155 dirty_counters: DirtyCounters::new(),
156 #[cfg(feature = "docking")]
157 docking_content_padding: 4.0,
158 }
159 }
160
161 #[cfg(feature = "docking")]
163 pub fn set_docking_content_padding(&mut self, padding: f32) {
164 self.docking_content_padding = padding;
165 }
166
167 pub fn add_widget(&mut self, widget: Box<dyn Widget>) -> NodeId {
169 let node_id = NodeId(self.next_id);
170 self.next_id += 1;
171
172 if widget.style().has_unresolved_constraints() {
174 self.viewport_constraint_nodes.insert(node_id);
175 }
176
177 let style = widget.style().layout.clone();
179 let taffy_node = self
180 .taffy
181 .new_leaf(style)
182 .expect("Failed to create taffy node");
183
184 let ui_node = UiNode {
185 widget,
186 taffy_node,
187 layout: LayoutRect {
188 x: 0.0,
189 y: 0.0,
190 width: 0.0,
191 height: 0.0,
192 },
193 dirty_flags: DirtyFlags::NONE,
194 parent: None,
195 children: Vec::new(),
196 text_measurement: None,
197 layout_version: 0,
198 text_version: 0,
199 paint_version: 0,
200 text_cache: None,
201 computed_z_index: 0,
202 computed_opacity: 1.0,
203 computed_translate: Vec2::ZERO,
204 computed_scale: Vec2::ONE,
205 };
206
207 self.nodes.insert(node_id, ui_node);
208 self.mark_dirty_flags(node_id, DirtyFlags::LAYOUT);
209
210 node_id
211 }
212
213 pub fn add_child(&mut self, parent: NodeId, child: NodeId) {
215 if let (Some(parent_node), Some(child_node)) =
216 (self.nodes.get(&parent), self.nodes.get(&child))
217 {
218 self.taffy
219 .add_child(parent_node.taffy_node, child_node.taffy_node)
220 .ok();
221
222 if let Some(child_node) = self.nodes.get_mut(&child) {
224 child_node.parent = Some(parent);
225 }
226 if let Some(parent_node) = self.nodes.get_mut(&parent) {
227 parent_node.children.push(child);
228 }
229
230 self.mark_dirty_flags(parent, DirtyFlags::CHILDREN_ORDER);
231 }
232 }
233
234 pub fn set_children(&mut self, parent: NodeId, children: &[NodeId]) {
236 if let Some(parent_node) = self.nodes.get(&parent) {
237 let taffy_children: Vec<taffy::NodeId> = children
238 .iter()
239 .filter_map(|id| self.nodes.get(id).map(|n| n.taffy_node))
240 .collect();
241
242 self.taffy
243 .set_children(parent_node.taffy_node, &taffy_children)
244 .ok();
245
246 for &child_id in children {
248 if let Some(child_node) = self.nodes.get_mut(&child_id) {
249 child_node.parent = Some(parent);
250 }
251 }
252 if let Some(parent_node) = self.nodes.get_mut(&parent) {
253 parent_node.children = children.to_vec();
254 }
255
256 self.mark_dirty_flags(parent, DirtyFlags::CHILDREN_ORDER);
257 }
258 }
259
260 pub fn set_root(&mut self, node_id: NodeId) {
262 self.root = Some(node_id);
263 self.mark_dirty_flags(node_id, DirtyFlags::LAYOUT);
264 }
265
266 fn is_layout_boundary(node: &UiNode) -> bool {
268 let style = &node.widget.style().layout;
269 matches!(style.size.width, Dimension::Length(_))
270 && matches!(style.size.height, Dimension::Length(_))
271 }
272
273 pub fn mark_dirty_flags(&mut self, node_id: NodeId, flags: DirtyFlags) {
275 profile_function!();
276
277 if flags.is_empty() {
278 return;
279 }
280
281 let needs_propagation = self.mark_node_dirty_inner(node_id, flags);
282
283 if needs_propagation {
285 self.propagate_dirty_to_ancestors(node_id, flags);
286 }
287 }
288
289 fn mark_node_dirty_inner(&mut self, node_id: NodeId, flags: DirtyFlags) -> bool {
292 self.dirty_nodes.insert(node_id);
293
294 let Some(node) = self.nodes.get_mut(&node_id) else {
295 return false;
296 };
297
298 let old_flags = node.dirty_flags;
299 node.dirty_flags |= flags;
300 self.dirty_counters.on_mark(old_flags, flags);
301
302 if flags
304 .intersects(DirtyFlags::LAYOUT | DirtyFlags::CHILDREN_ORDER | DirtyFlags::TEXT_SHAPING)
305 {
306 self.taffy.mark_dirty(node.taffy_node).ok();
307 }
308
309 if flags.intersects(DirtyFlags::LAYOUT | DirtyFlags::CHILDREN_ORDER) {
311 node.layout_version = node.layout_version.wrapping_add(1);
312 node.text_measurement = None;
314 }
315 if flags.contains(DirtyFlags::TEXT_SHAPING) {
316 node.text_version = node.text_version.wrapping_add(1);
317 node.text_measurement = None; node.text_cache = None; }
320 if flags.intersects(
321 DirtyFlags::COLOR
322 | DirtyFlags::OPACITY
323 | DirtyFlags::GEOMETRY
324 | DirtyFlags::IMAGE
325 | DirtyFlags::FOCUS
326 | DirtyFlags::SCROLL
327 | DirtyFlags::Z_INDEX
328 | DirtyFlags::TRANSFORM,
329 ) {
330 node.paint_version = node.paint_version.wrapping_add(1);
331 }
332 if flags.contains(DirtyFlags::VISIBILITY) {
333 node.layout_version = node.layout_version.wrapping_add(1);
334 }
335
336 flags.should_propagate_to_parent()
337 }
338
339 fn propagate_dirty_to_ancestors(&mut self, node_id: NodeId, flags: DirtyFlags) {
341 let propagation_flags = flags.propagation_flags();
342
343 let Some(node) = self.nodes.get(&node_id) else {
344 return;
345 };
346
347 if Self::is_layout_boundary(node) {
349 self.dirty_roots.insert(node_id);
350 return;
351 }
352
353 let mut current_parent = node.parent;
354
355 while let Some(parent_id) = current_parent {
356 if !self.dirty_nodes.insert(parent_id) {
357 if let Some(parent_node) = self.nodes.get(&parent_id)
359 && parent_node.dirty_flags.contains(propagation_flags)
360 {
361 break;
363 }
364 }
365
366 if let Some(parent_node) = self.nodes.get_mut(&parent_id) {
367 parent_node.dirty_flags |= propagation_flags;
368 if propagation_flags.contains(DirtyFlags::LAYOUT) {
369 parent_node.layout_version = parent_node.layout_version.wrapping_add(1);
370 }
371
372 if Self::is_layout_boundary(parent_node) {
373 self.dirty_roots.insert(parent_id);
374 return;
375 }
376
377 current_parent = parent_node.parent;
378 } else {
379 break;
380 }
381 }
382
383 if let Some(root) = self.root
385 && self.dirty_nodes.contains(&root)
386 {
387 self.dirty_roots.insert(root);
388 }
389 }
390
391 pub fn mark_dirty_batch(&mut self, updates: &[(NodeId, DirtyFlags)]) {
396 profile_function!();
397
398 if updates.is_empty() {
399 return;
400 }
401
402 let mut needs_propagation: Vec<(NodeId, DirtyFlags)> = Vec::new();
405 for &(node_id, flags) in updates {
406 if flags.is_empty() {
407 continue;
408 }
409 let needs_prop = self.mark_node_dirty_inner(node_id, flags);
410 if needs_prop {
411 needs_propagation.push((node_id, flags));
412 }
413 }
414
415 for (node_id, flags) in needs_propagation {
419 self.propagate_dirty_to_ancestors(node_id, flags);
420 }
421 }
422
423 pub fn mark_all_dirty_uniform(&mut self, flags: DirtyFlags) {
429 profile_function!();
430
431 if flags.is_empty() {
432 return;
433 }
434
435 let needs_propagation = flags.should_propagate_to_parent();
436
437 for (&node_id, node) in self.nodes.iter_mut() {
439 let old_flags = node.dirty_flags;
440 node.dirty_flags |= flags;
441 self.dirty_counters.on_mark(old_flags, flags);
442 self.dirty_nodes.insert(node_id);
443
444 if flags.intersects(
446 DirtyFlags::LAYOUT | DirtyFlags::CHILDREN_ORDER | DirtyFlags::TEXT_SHAPING,
447 ) {
448 self.taffy.mark_dirty(node.taffy_node).ok();
449 }
450
451 if flags.intersects(DirtyFlags::LAYOUT | DirtyFlags::CHILDREN_ORDER) {
453 node.layout_version = node.layout_version.wrapping_add(1);
454 node.text_measurement = None;
455 }
456 if flags.contains(DirtyFlags::TEXT_SHAPING) {
457 node.text_version = node.text_version.wrapping_add(1);
458 node.text_measurement = None;
459 node.text_cache = None;
460 }
461 if flags.intersects(
462 DirtyFlags::COLOR
463 | DirtyFlags::OPACITY
464 | DirtyFlags::GEOMETRY
465 | DirtyFlags::IMAGE
466 | DirtyFlags::FOCUS
467 | DirtyFlags::SCROLL
468 | DirtyFlags::Z_INDEX,
469 ) {
470 node.paint_version = node.paint_version.wrapping_add(1);
471 }
472 if flags.contains(DirtyFlags::VISIBILITY) {
473 node.layout_version = node.layout_version.wrapping_add(1);
474 }
475 }
476
477 if needs_propagation && let Some(root) = self.root {
479 self.dirty_roots.insert(root);
480 }
481 }
482
483 pub fn clear_dirty_flags(&mut self) {
487 for &node_id in &self.dirty_nodes {
488 if let Some(node) = self.nodes.get_mut(&node_id) {
489 self.dirty_counters.on_clear(node.dirty_flags);
490 node.dirty_flags = DirtyFlags::NONE;
491 }
492 }
493 self.dirty_nodes.clear();
494 self.dirty_roots.clear();
495 }
496
497 pub fn root(&self) -> Option<NodeId> {
499 self.root
500 }
501
502 pub fn get_widget(&self, node_id: NodeId) -> Option<&dyn Widget> {
504 self.nodes.get(&node_id).map(|n| &*n.widget)
505 }
506
507 pub fn get_widget_mut(&mut self, node_id: NodeId) -> Option<&mut dyn Widget> {
509 self.nodes.get_mut(&node_id).map(|n| &mut *n.widget)
510 }
511
512 pub fn get_layout(&self, node_id: NodeId) -> Option<LayoutRect> {
514 self.nodes.get(&node_id).map(|n| n.layout)
515 }
516
517 pub fn is_dirty(&self) -> bool {
519 !self.dirty_nodes.is_empty()
520 }
521
522 pub fn has_layout_dirty(&self) -> bool {
524 self.dirty_counters.has_layout_dirty()
525 }
526
527 pub fn has_text_dirty(&self) -> bool {
529 self.dirty_counters.has_text_dirty()
530 }
531
532 pub fn dirty_summary(&self) -> crate::dirty::DirtySummary {
534 self.dirty_counters.summary()
535 }
536
537 pub fn dirty_roots(&self) -> &HashSet<NodeId> {
542 &self.dirty_roots
543 }
544
545 pub fn dirty_nodes(&self) -> &HashSet<NodeId> {
547 &self.dirty_nodes
548 }
549
550 pub fn last_metrics(&self) -> Option<&UiMetrics> {
552 self.last_metrics.as_ref()
553 }
554
555 pub(crate) fn get_node(&self, node_id: NodeId) -> Option<&UiNode> {
557 self.nodes.get(&node_id)
558 }
559
560 pub(crate) fn get_node_mut(&mut self, node_id: NodeId) -> Option<&mut UiNode> {
562 self.nodes.get_mut(&node_id)
563 }
564
565 pub fn register_widget(&mut self, widget_id: crate::widget_id::WidgetId, node_id: NodeId) {
567 let _ = (widget_id, node_id);
571 }
572
573 pub fn style_guard_mut(&mut self, node_id: NodeId) -> StyleGuard<'_> {
587 StyleGuard::new(self, node_id)
588 }
589
590 pub fn update_text_content(&mut self, node_id: NodeId, new_content: impl Into<String>) -> bool {
594 if let Some(node) = self.nodes.get_mut(&node_id) {
595 if let Some(text) = node
597 .widget
598 .as_any_mut()
599 .downcast_mut::<crate::widgets::Text>()
600 {
601 let changed = text.set_content(new_content);
602 if changed {
603 self.mark_dirty_flags(node_id, DirtyFlags::TEXT_SHAPING);
604 }
605 return changed;
606 }
607 }
608 false
609 }
610
611 pub fn update_color(&mut self, node_id: NodeId, new_color: astrelis_render::Color) -> bool {
615 if let Some(node) = self.nodes.get_mut(&node_id) {
616 let old_color = node.widget.style().background_color;
617 node.widget.style_mut().background_color = Some(new_color);
618
619 if old_color != Some(new_color) {
620 self.mark_dirty_flags(node_id, DirtyFlags::COLOR);
621 return true;
622 }
623 }
624 false
625 }
626
627 pub fn update_opacity(&mut self, node_id: NodeId, opacity: f32) -> bool {
632 let opacity = opacity.clamp(0.0, 1.0);
633 if let Some(node) = self.nodes.get_mut(&node_id) {
634 let old = node.widget.style().opacity;
635 if (old - opacity).abs() < f32::EPSILON {
636 return false;
637 }
638 node.widget.style_mut().set_opacity(opacity);
639 self.mark_opacity_dirty(node_id);
640 true
641 } else {
642 false
643 }
644 }
645
646 pub fn update_translate(&mut self, node_id: NodeId, translate: Vec2) -> bool {
650 if let Some(node) = self.nodes.get_mut(&node_id) {
651 let old = node.widget.style().translate;
652 if old == translate {
653 return false;
654 }
655 node.widget.style_mut().set_translate(translate);
656 self.mark_transform_dirty(node_id);
657 true
658 } else {
659 false
660 }
661 }
662
663 pub fn update_translate_x(&mut self, node_id: NodeId, x: f32) -> bool {
665 if let Some(node) = self.nodes.get_mut(&node_id) {
666 let old = node.widget.style().translate.x;
667 if (old - x).abs() < f32::EPSILON {
668 return false;
669 }
670 node.widget.style_mut().set_translate_x(x);
671 self.mark_transform_dirty(node_id);
672 true
673 } else {
674 false
675 }
676 }
677
678 pub fn update_translate_y(&mut self, node_id: NodeId, y: f32) -> bool {
680 if let Some(node) = self.nodes.get_mut(&node_id) {
681 let old = node.widget.style().translate.y;
682 if (old - y).abs() < f32::EPSILON {
683 return false;
684 }
685 node.widget.style_mut().set_translate_y(y);
686 self.mark_transform_dirty(node_id);
687 true
688 } else {
689 false
690 }
691 }
692
693 pub fn update_scale(&mut self, node_id: NodeId, scale: Vec2) -> bool {
695 if let Some(node) = self.nodes.get_mut(&node_id) {
696 let old = node.widget.style().scale;
697 if old == scale {
698 return false;
699 }
700 node.widget.style_mut().set_scale(scale);
701 self.mark_transform_dirty(node_id);
702 true
703 } else {
704 false
705 }
706 }
707
708 pub fn update_scale_x(&mut self, node_id: NodeId, x: f32) -> bool {
710 if let Some(node) = self.nodes.get_mut(&node_id) {
711 let old = node.widget.style().scale.x;
712 if (old - x).abs() < f32::EPSILON {
713 return false;
714 }
715 node.widget.style_mut().scale.x = x;
716 self.mark_transform_dirty(node_id);
717 true
718 } else {
719 false
720 }
721 }
722
723 pub fn update_scale_y(&mut self, node_id: NodeId, y: f32) -> bool {
725 if let Some(node) = self.nodes.get_mut(&node_id) {
726 let old = node.widget.style().scale.y;
727 if (old - y).abs() < f32::EPSILON {
728 return false;
729 }
730 node.widget.style_mut().scale.y = y;
731 self.mark_transform_dirty(node_id);
732 true
733 } else {
734 false
735 }
736 }
737
738 pub fn set_visible(&mut self, node_id: NodeId, visible: bool) -> bool {
743 if let Some(node) = self.nodes.get_mut(&node_id) {
744 let old = node.widget.style().visible;
745 if old == visible {
746 return false;
747 }
748 node.widget.style_mut().set_visible(visible);
749
750 if visible {
752 node.widget.style_mut().layout.display = taffy::Display::Flex;
754 } else {
755 node.widget.style_mut().layout.display = taffy::Display::None;
756 }
757
758 let taffy_node = node.taffy_node;
760 let taffy_style = node.widget.style().layout.clone();
761 self.taffy.set_style(taffy_node, taffy_style).ok();
762
763 self.mark_dirty_flags(node_id, DirtyFlags::LAYOUT | DirtyFlags::VISIBILITY);
764 true
765 } else {
766 false
767 }
768 }
769
770 pub fn toggle_visible(&mut self, node_id: NodeId) -> bool {
772 if let Some(node) = self.nodes.get(&node_id) {
773 let current = node.widget.style().visible;
774 self.set_visible(node_id, !current)
775 } else {
776 false
777 }
778 }
779
780 pub fn compute_layout_instrumented(
783 &mut self,
784 viewport_size: astrelis_core::geometry::Size<f32>,
785 font_renderer: Option<&FontRenderer>,
786 widget_registry: &WidgetTypeRegistry,
787 ) -> UiMetrics {
788 profile_function!();
789
790 let total_timer = MetricsTimer::start();
791 let mut metrics = UiMetrics::new();
792 metrics.total_nodes = self.nodes.len();
793
794 let mut dirty_stats = DirtyStats::new();
796 for node in self.nodes.values() {
797 dirty_stats.add_node(node.dirty_flags);
798 }
799 metrics.nodes_layout_dirty = dirty_stats.layout_count;
800 metrics.nodes_text_dirty = dirty_stats.text_count;
801 metrics.nodes_paint_dirty = dirty_stats.paint_count;
802 metrics.nodes_geometry_dirty = dirty_stats.geometry_count;
803
804 if self.dirty_nodes.is_empty() {
806 metrics.total_time = total_timer.stop();
807 self.last_metrics = Some(metrics.clone());
808 return metrics;
809 }
810
811 if !self.has_layout_dirty() {
813 metrics.layout_skips = self.nodes.len();
814 metrics.total_time = total_timer.stop();
815 self.last_metrics = Some(metrics.clone());
816 return metrics;
817 }
818
819 let layout_timer = MetricsTimer::start();
820 self.compute_layout_internal(viewport_size, font_renderer, widget_registry);
821 metrics.layout_time = layout_timer.stop();
822
823 metrics.total_time = total_timer.stop();
824 self.last_metrics = Some(metrics.clone());
825 metrics
826 }
827
828 pub fn compute_layout(
830 &mut self,
831 size: astrelis_core::geometry::Size<f32>,
832 font_renderer: Option<&FontRenderer>,
833 widget_registry: &WidgetTypeRegistry,
834 ) {
835 profile_function!();
836
837 if self.dirty_nodes.is_empty() {
839 return;
840 }
841
842 if !self.has_layout_dirty() {
845 return;
846 }
847
848 self.compute_layout_internal(size, font_renderer, widget_registry);
849 }
851
852 fn compute_layout_internal(
859 &mut self,
860 viewport_size: astrelis_core::geometry::Size<f32>,
861 font_renderer: Option<&FontRenderer>,
862 widget_registry: &WidgetTypeRegistry,
863 ) {
864 profile_scope!("compute_layout_internal");
865
866 self.resolve_viewport_units(viewport_size);
868
869 let Some(root_id) = self.root else { return };
871 let Some(root_node) = self.nodes.get(&root_id) else {
872 return;
873 };
874 let root_taffy_node = root_node.taffy_node;
875
876 let available_space = Size {
877 width: AvailableSpace::Definite(viewport_size.width),
878 height: AvailableSpace::Definite(viewport_size.height),
879 };
880
881 let nodes_ptr = &mut self.nodes as *mut IndexMap<NodeId, UiNode>;
882
883 let measure_func = |known_dimensions: Size<Option<f32>>,
884 available_space: Size<AvailableSpace>,
885 node_id: taffy::NodeId,
886 _node_context: Option<&mut ()>,
887 _style: &taffy::Style|
888 -> Size<f32> {
889 let nodes = unsafe { &mut *nodes_ptr };
891
892 let (widget, cached_measurement) = nodes
893 .values_mut()
894 .find(|node| node.taffy_node == node_id)
895 .map(|node| (&node.widget, &mut node.text_measurement))
896 .unzip();
897
898 if let (Some(widget), Some(cached_measurement)) = (widget, cached_measurement) {
899 if let Some((cached_w, cached_h)) = *cached_measurement {
900 return Size {
901 width: known_dimensions.width.unwrap_or(cached_w),
902 height: known_dimensions.height.unwrap_or(cached_h),
903 };
904 }
905
906 let available = Vec2::new(
907 match available_space.width {
908 AvailableSpace::Definite(w) => w,
909 AvailableSpace::MinContent => 0.0,
910 AvailableSpace::MaxContent => f32::MAX,
911 },
912 match available_space.height {
913 AvailableSpace::Definite(h) => h,
914 AvailableSpace::MinContent => 0.0,
915 AvailableSpace::MaxContent => f32::MAX,
916 },
917 );
918
919 let measured = widget.measure(available, font_renderer);
920
921 if widget_registry.caches_measurement(widget.as_any().type_id()) {
923 *cached_measurement = Some((measured.x, measured.y));
924 }
925
926 Size {
927 width: known_dimensions.width.unwrap_or(measured.x),
928 height: known_dimensions.height.unwrap_or(measured.y),
929 }
930 } else {
931 Size {
932 width: known_dimensions.width.unwrap_or(0.0),
933 height: known_dimensions.height.unwrap_or(0.0),
934 }
935 }
936 };
937
938 self.taffy
939 .compute_layout_with_measure(root_taffy_node, available_space, measure_func)
940 .ok();
941
942 self.update_subtree_layout(root_id);
944
945 #[cfg(feature = "docking")]
947 self.post_process_docking_layouts(root_id);
948 }
949
950 #[cfg(feature = "docking")]
959 fn post_process_docking_layouts(&mut self, node_id: NodeId) {
960 profile_scope!("post_process_docking_layouts");
961
962 let info = {
964 let Some(node) = self.nodes.get(&node_id) else {
965 return;
966 };
967
968 node.widget
969 .as_any()
970 .downcast_ref::<DockSplitter>()
971 .map(|splitter| DockingLayoutInfo::Splitter {
972 children: splitter.children.clone(),
973 direction: splitter.direction,
974 split_ratio: splitter.split_ratio,
975 separator_size: splitter.separator_size,
976 parent_layout: node.layout,
977 })
978 .or_else(|| {
979 node.widget.as_any().downcast_ref::<DockTabs>().map(|tabs| {
980 let content_padding =
981 tabs.content_padding.unwrap_or(self.docking_content_padding);
982 DockingLayoutInfo::Tabs {
983 children: tabs.children.clone(),
984 active_tab: tabs.active_tab,
985 tab_bar_height: tabs.theme.tab_bar_height,
986 content_padding,
987 parent_layout: node.layout,
988 }
989 })
990 })
991 };
992
993 let children_to_recurse = match info {
995 Some(DockingLayoutInfo::Splitter {
996 children,
997 direction,
998 split_ratio,
999 separator_size,
1000 parent_layout,
1001 }) => {
1002 self.apply_splitter_layout(
1003 children.clone(),
1004 direction,
1005 split_ratio,
1006 separator_size,
1007 parent_layout,
1008 );
1009 children
1010 }
1011 Some(DockingLayoutInfo::Tabs {
1012 children,
1013 active_tab,
1014 tab_bar_height,
1015 content_padding,
1016 parent_layout,
1017 }) => {
1018 self.apply_tabs_layout(
1019 children.clone(),
1020 active_tab,
1021 tab_bar_height,
1022 content_padding,
1023 parent_layout,
1024 );
1025 children
1026 }
1027 None => {
1028 let Some(node) = self.nodes.get(&node_id) else {
1030 return;
1031 };
1032 node.children.clone()
1033 }
1034 };
1035
1036 for child_id in children_to_recurse {
1038 self.post_process_docking_layouts(child_id);
1039 }
1040 }
1041
1042 #[cfg(feature = "docking")]
1044 fn apply_splitter_layout(
1045 &mut self,
1046 children: Vec<NodeId>,
1047 direction: crate::widgets::docking::SplitDirection,
1048 split_ratio: f32,
1049 separator_size: f32,
1050 parent_layout: LayoutRect,
1051 ) {
1052 if children.len() < 2 {
1053 return;
1054 }
1055
1056 let half_sep = separator_size / 2.0;
1057
1058 match direction {
1059 crate::widgets::docking::SplitDirection::Horizontal => {
1060 let split_x = parent_layout.width * split_ratio;
1062
1063 if let Some(node) = self.nodes.get_mut(&children[0]) {
1065 node.layout = LayoutRect {
1066 x: 0.0,
1067 y: 0.0,
1068 width: (split_x - half_sep).max(0.0),
1069 height: parent_layout.height,
1070 };
1071 }
1072
1073 if let Some(node) = self.nodes.get_mut(&children[1]) {
1075 node.layout = LayoutRect {
1076 x: split_x + half_sep,
1077 y: 0.0,
1078 width: (parent_layout.width - split_x - half_sep).max(0.0),
1079 height: parent_layout.height,
1080 };
1081 }
1082 }
1083 crate::widgets::docking::SplitDirection::Vertical => {
1084 let split_y = parent_layout.height * split_ratio;
1086
1087 if let Some(node) = self.nodes.get_mut(&children[0]) {
1089 node.layout = LayoutRect {
1090 x: 0.0,
1091 y: 0.0,
1092 width: parent_layout.width,
1093 height: (split_y - half_sep).max(0.0),
1094 };
1095 }
1096
1097 if let Some(node) = self.nodes.get_mut(&children[1]) {
1099 node.layout = LayoutRect {
1100 x: 0.0,
1101 y: split_y + half_sep,
1102 width: parent_layout.width,
1103 height: (parent_layout.height - split_y - half_sep).max(0.0),
1104 };
1105 }
1106 }
1107 }
1108 }
1109
1110 #[cfg(feature = "docking")]
1112 fn apply_tabs_layout(
1113 &mut self,
1114 children: Vec<NodeId>,
1115 _active_tab: usize,
1116 tab_bar_height: f32,
1117 content_padding: f32,
1118 parent_layout: LayoutRect,
1119 ) {
1120 let content_layout = LayoutRect {
1122 x: content_padding,
1123 y: tab_bar_height + content_padding,
1124 width: (parent_layout.width - content_padding * 2.0).max(0.0),
1125 height: (parent_layout.height - tab_bar_height - content_padding * 2.0).max(0.0),
1126 };
1127
1128 for child_id in &children {
1131 if let Some(node) = self.nodes.get_mut(child_id) {
1132 node.layout = content_layout;
1133 }
1134 }
1135 }
1136
1137 fn resolve_viewport_units(&mut self, viewport_size: astrelis_core::geometry::Size<f32>) {
1142 profile_scope!("resolve_viewport_units");
1143
1144 if self.viewport_constraint_nodes.is_empty() {
1145 return;
1146 }
1147
1148 let viewport = Vec2::new(viewport_size.width, viewport_size.height);
1149 let ctx = ResolveContext::viewport_only(viewport);
1150
1151 let constraint_nodes: Vec<NodeId> =
1153 self.viewport_constraint_nodes.iter().copied().collect();
1154
1155 for node_id in constraint_nodes {
1156 if let Some(node) = self.nodes.get_mut(&node_id) {
1157 let style = node.widget.style_mut();
1158 let mut changed = false;
1159
1160 if let Some(ref constraints) = style.constraints {
1162 if let Some(ref constraint) = constraints.width
1164 && constraint.needs_resolution()
1165 && let Some(px) = ConstraintResolver::resolve(constraint, &ctx)
1166 {
1167 style.layout.size.width = taffy::Dimension::Length(px);
1168 changed = true;
1169 }
1170
1171 if let Some(ref constraint) = constraints.height
1173 && constraint.needs_resolution()
1174 && let Some(px) = ConstraintResolver::resolve(constraint, &ctx)
1175 {
1176 style.layout.size.height = taffy::Dimension::Length(px);
1177 changed = true;
1178 }
1179
1180 if let Some(ref constraint) = constraints.min_width
1182 && constraint.needs_resolution()
1183 && let Some(px) = ConstraintResolver::resolve(constraint, &ctx)
1184 {
1185 style.layout.min_size.width = taffy::Dimension::Length(px);
1186 changed = true;
1187 }
1188
1189 if let Some(ref constraint) = constraints.min_height
1191 && constraint.needs_resolution()
1192 && let Some(px) = ConstraintResolver::resolve(constraint, &ctx)
1193 {
1194 style.layout.min_size.height = taffy::Dimension::Length(px);
1195 changed = true;
1196 }
1197
1198 if let Some(ref constraint) = constraints.max_width
1200 && constraint.needs_resolution()
1201 && let Some(px) = ConstraintResolver::resolve(constraint, &ctx)
1202 {
1203 style.layout.max_size.width = taffy::Dimension::Length(px);
1204 changed = true;
1205 }
1206
1207 if let Some(ref constraint) = constraints.max_height
1209 && constraint.needs_resolution()
1210 && let Some(px) = ConstraintResolver::resolve(constraint, &ctx)
1211 {
1212 style.layout.max_size.height = taffy::Dimension::Length(px);
1213 changed = true;
1214 }
1215
1216 if let Some(ref padding) = constraints.padding
1218 && padding.iter().any(|c| c.needs_resolution())
1219 {
1220 if let Some(px) = ConstraintResolver::resolve(&padding[0], &ctx) {
1221 style.layout.padding.left = taffy::LengthPercentage::Length(px);
1222 changed = true;
1223 }
1224 if let Some(px) = ConstraintResolver::resolve(&padding[1], &ctx) {
1225 style.layout.padding.top = taffy::LengthPercentage::Length(px);
1226 changed = true;
1227 }
1228 if let Some(px) = ConstraintResolver::resolve(&padding[2], &ctx) {
1229 style.layout.padding.right = taffy::LengthPercentage::Length(px);
1230 changed = true;
1231 }
1232 if let Some(px) = ConstraintResolver::resolve(&padding[3], &ctx) {
1233 style.layout.padding.bottom = taffy::LengthPercentage::Length(px);
1234 changed = true;
1235 }
1236 }
1237
1238 if let Some(ref margin) = constraints.margin
1240 && margin.iter().any(|c| c.needs_resolution())
1241 {
1242 if let Some(px) = ConstraintResolver::resolve(&margin[0], &ctx) {
1243 style.layout.margin.left = taffy::LengthPercentageAuto::Length(px);
1244 changed = true;
1245 }
1246 if let Some(px) = ConstraintResolver::resolve(&margin[1], &ctx) {
1247 style.layout.margin.top = taffy::LengthPercentageAuto::Length(px);
1248 changed = true;
1249 }
1250 if let Some(px) = ConstraintResolver::resolve(&margin[2], &ctx) {
1251 style.layout.margin.right = taffy::LengthPercentageAuto::Length(px);
1252 changed = true;
1253 }
1254 if let Some(px) = ConstraintResolver::resolve(&margin[3], &ctx) {
1255 style.layout.margin.bottom = taffy::LengthPercentageAuto::Length(px);
1256 changed = true;
1257 }
1258 }
1259
1260 if let Some(ref constraint) = constraints.gap
1262 && constraint.needs_resolution()
1263 && let Some(px) = ConstraintResolver::resolve(constraint, &ctx)
1264 {
1265 style.layout.gap.width = taffy::LengthPercentage::Length(px);
1266 style.layout.gap.height = taffy::LengthPercentage::Length(px);
1267 changed = true;
1268 }
1269
1270 if let Some(ref constraint) = constraints.flex_basis
1272 && constraint.needs_resolution()
1273 && let Some(px) = ConstraintResolver::resolve(constraint, &ctx)
1274 {
1275 style.layout.flex_basis = taffy::Dimension::Length(px);
1276 changed = true;
1277 }
1278 }
1279
1280 if changed {
1282 let taffy_node = node.taffy_node;
1283 let layout_style = style.layout.clone();
1284 self.taffy.set_style(taffy_node, layout_style).ok();
1285 self.taffy.mark_dirty(taffy_node).ok();
1286 }
1287 }
1288 }
1289 }
1290
1291 pub fn mark_viewport_dirty(&mut self) {
1295 let updates: Vec<(NodeId, DirtyFlags)> = self
1296 .viewport_constraint_nodes
1297 .iter()
1298 .map(|&id| (id, DirtyFlags::LAYOUT))
1299 .collect();
1300 self.mark_dirty_batch(&updates);
1301 }
1302
1303 pub fn mark_all_dirty(&mut self, flags: DirtyFlags) {
1305 self.mark_all_dirty_uniform(flags);
1306 }
1307
1308 #[allow(dead_code)]
1310 fn cache_layouts(&mut self) {
1311 let node_ids: Vec<NodeId> = self.nodes.keys().copied().collect();
1312
1313 for node_id in node_ids {
1314 if let Some(node) = self.nodes.get(&node_id)
1315 && let Ok(layout) = self.taffy.layout(node.taffy_node)
1316 {
1317 let layout_rect = LayoutRect {
1318 x: layout.location.x,
1319 y: layout.location.y,
1320 width: layout.size.width,
1321 height: layout.size.height,
1322 };
1323
1324 if let Some(node) = self.nodes.get_mut(&node_id) {
1325 node.layout = layout_rect;
1326 }
1327 }
1328 }
1329 }
1330
1331 fn update_subtree_layout(&mut self, root_id: NodeId) {
1336 let mut stack: Vec<(NodeId, u16, f32, Vec2, Vec2)> =
1338 vec![(root_id, 0, 1.0, Vec2::ZERO, Vec2::ONE)];
1339
1340 while let Some((node_id, parent_z, parent_opacity, parent_translate, parent_scale)) =
1341 stack.pop()
1342 {
1343 let (z_offset, opacity, translate, node_scale, children) =
1345 if let Some(node) = self.nodes.get(&node_id) {
1346 let s = node.widget.style();
1347 (
1348 s.z_index,
1349 s.opacity,
1350 s.translate,
1351 s.scale,
1352 node.children.clone(),
1353 )
1354 } else {
1355 continue;
1356 };
1357
1358 let computed_z = parent_z.saturating_add(z_offset);
1360 let computed_opacity = parent_opacity * opacity;
1361 let computed_translate = parent_translate + translate;
1362 let computed_scale =
1363 Vec2::new(parent_scale.x * node_scale.x, parent_scale.y * node_scale.y);
1364
1365 if let Some(node) = self.nodes.get_mut(&node_id)
1367 && let Ok(layout) = self.taffy.layout(node.taffy_node)
1368 {
1369 node.layout = LayoutRect {
1370 x: layout.location.x,
1371 y: layout.location.y,
1372 width: layout.size.width,
1373 height: layout.size.height,
1374 };
1375 node.computed_z_index = computed_z;
1376 node.computed_opacity = computed_opacity;
1377 node.computed_translate = computed_translate;
1378 node.computed_scale = computed_scale;
1379 }
1380
1381 for child_id in children {
1383 stack.push((
1384 child_id,
1385 computed_z,
1386 computed_opacity,
1387 computed_translate,
1388 computed_scale,
1389 ));
1390 }
1391 }
1392 }
1393
1394 pub fn mark_z_index_dirty(&mut self, node_id: NodeId) {
1400 self.mark_descendants_dirty(node_id, DirtyFlags::Z_INDEX);
1401 }
1402
1403 pub fn mark_opacity_dirty(&mut self, node_id: NodeId) {
1408 self.mark_descendants_dirty(node_id, DirtyFlags::OPACITY);
1409 }
1410
1411 pub fn mark_transform_dirty(&mut self, node_id: NodeId) {
1416 self.mark_descendants_dirty(node_id, DirtyFlags::TRANSFORM);
1417 }
1418
1419 fn mark_descendants_dirty(&mut self, node_id: NodeId, flag: DirtyFlags) {
1423 let mut stack = vec![node_id];
1424 while let Some(id) = stack.pop() {
1425 self.dirty_nodes.insert(id);
1426 if let Some(node) = self.nodes.get_mut(&id) {
1427 let old_flags = node.dirty_flags;
1428 node.dirty_flags |= flag;
1429 self.dirty_counters.on_mark(old_flags, flag);
1430 node.paint_version = node.paint_version.wrapping_add(1);
1431 stack.extend(node.children.iter().copied());
1432 }
1433 }
1434 }
1435
1436 pub fn clear(&mut self) {
1438 self.nodes.clear();
1439 self.taffy.clear();
1440 self.root = None;
1441 self.next_id = 0;
1442 self.dirty_nodes.clear();
1443 self.dirty_roots.clear();
1444 self.dirty_counters.reset();
1445 self.viewport_constraint_nodes.clear();
1446 self.removed_nodes.clear();
1447 }
1448
1449 pub fn drain_removed_nodes(&mut self) -> Vec<NodeId> {
1454 std::mem::take(&mut self.removed_nodes)
1455 }
1456
1457 pub fn node_exists(&self, node_id: NodeId) -> bool {
1459 self.nodes.contains_key(&node_id)
1460 }
1461
1462 pub(crate) fn sync_taffy_style(&mut self, node_id: NodeId) {
1467 if let Some(node) = self.nodes.get(&node_id) {
1468 let taffy_node = node.taffy_node;
1469 let layout_style = node.widget.style().layout.clone();
1470 self.taffy.set_style(taffy_node, layout_style).ok();
1471 self.taffy.mark_dirty(taffy_node).ok();
1472 }
1473 }
1474
1475 pub fn find_widgets_with_layout<T: 'static>(&self) -> Vec<(NodeId, LayoutRect)> {
1480 let mut results = Vec::new();
1481 if let Some(root) = self.root {
1482 self.find_widgets_recursive::<T>(root, Vec2::ZERO, &mut results);
1483 }
1484 results
1485 }
1486
1487 fn find_widgets_recursive<T: 'static>(
1489 &self,
1490 node_id: NodeId,
1491 parent_offset: Vec2,
1492 results: &mut Vec<(NodeId, LayoutRect)>,
1493 ) {
1494 let Some(node) = self.nodes.get(&node_id) else {
1495 return;
1496 };
1497
1498 let abs_x = parent_offset.x + node.layout.x;
1499 let abs_y = parent_offset.y + node.layout.y;
1500
1501 if node.widget.as_any().downcast_ref::<T>().is_some() {
1503 results.push((
1504 node_id,
1505 LayoutRect {
1506 x: abs_x,
1507 y: abs_y,
1508 width: node.layout.width,
1509 height: node.layout.height,
1510 },
1511 ));
1512 }
1513
1514 let children = node.children.clone();
1516 let offset = Vec2::new(abs_x, abs_y);
1517 for child_id in children {
1518 self.find_widgets_recursive::<T>(child_id, offset, results);
1519 }
1520 }
1521
1522 pub fn iter(&self) -> impl Iterator<Item = (NodeId, &UiNode)> {
1524 self.nodes.iter().map(|(id, node)| (*id, node))
1525 }
1526
1527 pub fn iter_mut(&mut self) -> impl Iterator<Item = (NodeId, &mut UiNode)> {
1529 self.nodes.iter_mut().map(|(id, node)| (*id, node))
1530 }
1531
1532 pub fn node_ids(&self) -> Vec<NodeId> {
1534 self.nodes.keys().copied().collect()
1535 }
1536
1537 pub fn update_style(&mut self, node_id: NodeId, style: Style) {
1539 if let Some(node) = self.nodes.get_mut(&node_id) {
1540 *node.widget.style_mut() = style.clone();
1541 self.taffy.set_style(node.taffy_node, style.layout).ok();
1542 self.mark_dirty_flags(node_id, DirtyFlags::LAYOUT);
1543 }
1544 }
1545
1546 pub fn remove_node(&mut self, node_id: NodeId) -> bool {
1568 if !self.nodes.contains_key(&node_id) {
1570 return false;
1571 }
1572
1573 let mut to_remove = Vec::new();
1575 let mut stack = vec![node_id];
1576
1577 while let Some(id) = stack.pop() {
1578 to_remove.push(id);
1579 if let Some(node) = self.nodes.get(&id) {
1580 stack.extend(node.children.iter().copied());
1581 }
1582 }
1583
1584 if let Some(node) = self.nodes.get(&node_id)
1586 && let Some(parent_id) = node.parent
1587 && let Some(parent_node) = self.nodes.get_mut(&parent_id)
1588 {
1589 parent_node.children.retain(|&child| child != node_id);
1590 self.mark_dirty_flags(parent_id, DirtyFlags::CHILDREN_ORDER);
1592 }
1593
1594 for id in to_remove.iter().rev() {
1596 if let Some(node) = self.nodes.shift_remove(id) {
1597 self.taffy.remove(node.taffy_node).ok();
1599 if !node.dirty_flags.is_empty() {
1601 self.dirty_counters.on_clear(node.dirty_flags);
1602 }
1603 self.dirty_nodes.remove(id);
1605 self.dirty_roots.remove(id);
1606 self.viewport_constraint_nodes.remove(id);
1608 self.removed_nodes.push(*id);
1610 }
1611 }
1612
1613 if self.root == Some(node_id) {
1615 self.root = None;
1616 }
1617
1618 true
1619 }
1620
1621 pub fn remove_child(&mut self, parent: NodeId, child: NodeId) -> bool {
1634 let (parent_taffy, child_taffy) = match (self.nodes.get(&parent), self.nodes.get(&child)) {
1636 (Some(p), Some(c)) => (p.taffy_node, c.taffy_node),
1637 _ => return false,
1638 };
1639
1640 let had_child = self
1642 .nodes
1643 .get(&parent)
1644 .map(|p| p.children.contains(&child))
1645 .unwrap_or(false);
1646
1647 if !had_child {
1648 return false;
1649 }
1650
1651 if let Some(parent_node) = self.nodes.get_mut(&parent) {
1653 parent_node.children.retain(|&c| c != child);
1654 }
1655
1656 self.taffy.remove_child(parent_taffy, child_taffy).ok();
1658
1659 if let Some(child_node) = self.nodes.get_mut(&child) {
1661 child_node.parent = None;
1662 }
1663
1664 self.mark_dirty_flags(parent, DirtyFlags::CHILDREN_ORDER);
1665 true
1666 }
1667
1668 pub fn set_position_offset(&mut self, node_id: NodeId, x_offset: f32, y_offset: f32) {
1687 if let Some(node) = self.nodes.get_mut(&node_id) {
1688 if let Ok(layout) = self.taffy.layout(node.taffy_node) {
1691 node.layout.x = layout.location.x + x_offset;
1693 node.layout.y = layout.location.y + y_offset;
1694 }
1695 }
1696 }
1697
1698 pub fn get_base_position(&self, node_id: NodeId) -> Option<(f32, f32)> {
1702 self.nodes.get(&node_id).and_then(|node| {
1703 self.taffy
1704 .layout(node.taffy_node)
1705 .ok()
1706 .map(|layout| (layout.location.x, layout.location.y))
1707 })
1708 }
1709}
1710
1711impl Default for UiTree {
1712 fn default() -> Self {
1713 Self::new()
1714 }
1715}