1pub mod cache;
6pub mod calc;
7pub mod counters;
8pub mod display_list;
9pub mod fc;
10pub mod geometry;
11pub mod getters;
12pub mod layout_tree;
13pub mod paged_layout;
14pub mod pagination;
15pub mod positioning;
16pub mod scrollbar;
17pub mod sizing;
18pub mod taffy_bridge;
19
20#[macro_export]
22macro_rules! debug_info {
23 ($ctx:expr, $($arg:tt)*) => {
24 if $ctx.debug_messages.is_some() {
25 $ctx.debug_info_inner(format!($($arg)*));
26 }
27 };
28}
29
30#[macro_export]
32macro_rules! debug_warning {
33 ($ctx:expr, $($arg:tt)*) => {
34 if $ctx.debug_messages.is_some() {
35 $ctx.debug_warning_inner(format!($($arg)*));
36 }
37 };
38}
39
40#[macro_export]
42macro_rules! debug_error {
43 ($ctx:expr, $($arg:tt)*) => {
44 if $ctx.debug_messages.is_some() {
45 $ctx.debug_error_inner(format!($($arg)*));
46 }
47 };
48}
49
50#[macro_export]
52macro_rules! debug_log {
53 ($ctx:expr, $($arg:tt)*) => {
54 if $ctx.debug_messages.is_some() {
55 $ctx.debug_log_inner(format!($($arg)*));
56 }
57 };
58}
59
60#[macro_export]
62macro_rules! debug_box_props {
63 ($ctx:expr, $($arg:tt)*) => {
64 if $ctx.debug_messages.is_some() {
65 $ctx.debug_box_props_inner(format!($($arg)*));
66 }
67 };
68}
69
70#[macro_export]
72macro_rules! debug_css_getter {
73 ($ctx:expr, $($arg:tt)*) => {
74 if $ctx.debug_messages.is_some() {
75 $ctx.debug_css_getter_inner(format!($($arg)*));
76 }
77 };
78}
79
80#[macro_export]
82macro_rules! debug_bfc_layout {
83 ($ctx:expr, $($arg:tt)*) => {
84 if $ctx.debug_messages.is_some() {
85 $ctx.debug_bfc_layout_inner(format!($($arg)*));
86 }
87 };
88}
89
90#[macro_export]
92macro_rules! debug_ifc_layout {
93 ($ctx:expr, $($arg:tt)*) => {
94 if $ctx.debug_messages.is_some() {
95 $ctx.debug_ifc_layout_inner(format!($($arg)*));
96 }
97 };
98}
99
100#[macro_export]
102macro_rules! debug_table_layout {
103 ($ctx:expr, $($arg:tt)*) => {
104 if $ctx.debug_messages.is_some() {
105 $ctx.debug_table_layout_inner(format!($($arg)*));
106 }
107 };
108}
109
110#[macro_export]
112macro_rules! debug_display_type {
113 ($ctx:expr, $($arg:tt)*) => {
114 if $ctx.debug_messages.is_some() {
115 $ctx.debug_display_type_inner(format!($($arg)*));
116 }
117 };
118}
119
120use std::{collections::BTreeMap, sync::Arc};
127
128use azul_core::{
129 dom::{DomId, NodeId},
130 geom::{LogicalPosition, LogicalRect, LogicalSize},
131 hit_test::{DocumentId, ScrollPosition},
132 resources::RendererResources,
133 selection::{SelectionState, TextCursor, TextSelection},
134 styled_dom::StyledDom,
135};
136
137pub const POSITION_UNSET: LogicalPosition = LogicalPosition { x: f32::MIN, y: f32::MIN };
139
140pub type PositionVec = Vec<LogicalPosition>;
143
144#[inline]
146pub fn new_position_vec(num_nodes: usize) -> PositionVec {
147 vec![POSITION_UNSET; num_nodes]
148}
149
150#[inline(always)]
152pub fn pos_get(positions: &PositionVec, idx: usize) -> Option<LogicalPosition> {
153 positions.get(idx).copied().filter(|p| p.x != f32::MIN)
154}
155
156#[inline(always)]
158pub fn pos_set(positions: &mut PositionVec, idx: usize, pos: LogicalPosition) {
159 if idx >= positions.len() {
160 positions.resize(idx + 1, POSITION_UNSET);
161 }
162 positions[idx] = pos;
163}
164
165#[inline(always)]
167pub fn pos_contains(positions: &PositionVec, idx: usize) -> bool {
168 positions.get(idx).map_or(false, |p| p.x != f32::MIN)
169}
170use azul_css::{
171 props::property::{CssProperty, CssPropertyCategory},
172 LayoutDebugMessage, LayoutDebugMessageType,
173};
174
175use self::{
176 display_list::generate_display_list,
177 geometry::IntrinsicSizes,
178 getters::get_writing_mode,
179 layout_tree::{generate_layout_tree, LayoutTree},
180 sizing::calculate_intrinsic_sizes,
181};
182#[cfg(feature = "text_layout")]
183pub use crate::font_traits::TextLayoutCache;
184use crate::{
185 font_traits::ParsedFontTrait,
186 solver3::{
187 cache::LayoutCache,
188 display_list::DisplayList,
189 fc::{LayoutConstraints, LayoutResult},
190 layout_tree::DirtyFlag,
191 },
192};
193
194pub type NodeHashMap = BTreeMap<usize, u64>;
196
197pub struct LayoutContext<'a, T: ParsedFontTrait> {
199 pub styled_dom: &'a StyledDom,
200 #[cfg(feature = "text_layout")]
201 pub font_manager: &'a crate::font_traits::FontManager<T>,
202 #[cfg(not(feature = "text_layout"))]
203 pub font_manager: core::marker::PhantomData<&'a T>,
204 pub selections: &'a BTreeMap<DomId, SelectionState>,
206 pub text_selections: &'a BTreeMap<DomId, TextSelection>,
208 pub debug_messages: &'a mut Option<Vec<LayoutDebugMessage>>,
209 pub counters: &'a mut BTreeMap<(usize, String), i32>,
210 pub viewport_size: LogicalSize,
211 pub fragmentation_context: Option<&'a mut crate::paged::FragmentationContext>,
214 pub cursor_is_visible: bool,
218 pub cursor_location: Option<(DomId, NodeId, TextCursor)>,
222 pub cache_map: cache::LayoutCacheMap,
226 pub system_style: Option<std::sync::Arc<azul_css::system::SystemStyle>>,
229 pub get_system_time_fn: azul_core::task::GetSystemTimeCallback,
233}
234
235impl<'a, T: ParsedFontTrait> LayoutContext<'a, T> {
236 #[inline]
238 pub fn has_debug(&self) -> bool {
239 self.debug_messages.is_some()
240 }
241
242 #[inline]
244 pub fn debug_log_inner(&mut self, message: String) {
245 if let Some(messages) = self.debug_messages.as_mut() {
246 messages.push(LayoutDebugMessage {
247 message: message.into(),
248 location: "solver3".into(),
249 message_type: Default::default(),
250 });
251 }
252 }
253
254 #[inline]
256 pub fn debug_info_inner(&mut self, message: String) {
257 if let Some(messages) = self.debug_messages.as_mut() {
258 messages.push(LayoutDebugMessage::info(message));
259 }
260 }
261
262 #[inline]
264 pub fn debug_warning_inner(&mut self, message: String) {
265 if let Some(messages) = self.debug_messages.as_mut() {
266 messages.push(LayoutDebugMessage::warning(message));
267 }
268 }
269
270 #[inline]
272 pub fn debug_error_inner(&mut self, message: String) {
273 if let Some(messages) = self.debug_messages.as_mut() {
274 messages.push(LayoutDebugMessage::error(message));
275 }
276 }
277
278 #[inline]
280 pub fn debug_box_props_inner(&mut self, message: String) {
281 if let Some(messages) = self.debug_messages.as_mut() {
282 messages.push(LayoutDebugMessage::box_props(message));
283 }
284 }
285
286 #[inline]
288 pub fn debug_css_getter_inner(&mut self, message: String) {
289 if let Some(messages) = self.debug_messages.as_mut() {
290 messages.push(LayoutDebugMessage::css_getter(message));
291 }
292 }
293
294 #[inline]
296 pub fn debug_bfc_layout_inner(&mut self, message: String) {
297 if let Some(messages) = self.debug_messages.as_mut() {
298 messages.push(LayoutDebugMessage::bfc_layout(message));
299 }
300 }
301
302 #[inline]
304 pub fn debug_ifc_layout_inner(&mut self, message: String) {
305 if let Some(messages) = self.debug_messages.as_mut() {
306 messages.push(LayoutDebugMessage::ifc_layout(message));
307 }
308 }
309
310 #[inline]
312 pub fn debug_table_layout_inner(&mut self, message: String) {
313 if let Some(messages) = self.debug_messages.as_mut() {
314 messages.push(LayoutDebugMessage::table_layout(message));
315 }
316 }
317
318 #[inline]
320 pub fn debug_display_type_inner(&mut self, message: String) {
321 if let Some(messages) = self.debug_messages.as_mut() {
322 messages.push(LayoutDebugMessage::display_type(message));
323 }
324 }
325
326 #[inline]
330 #[deprecated(note = "Use debug_info! macro for lazy evaluation")]
331 #[allow(deprecated)]
332 pub fn debug_info(&mut self, message: impl Into<String>) {
333 self.debug_info_inner(message.into());
334 }
335
336 #[inline]
337 #[deprecated(note = "Use debug_warning! macro for lazy evaluation")]
338 #[allow(deprecated)]
339 pub fn debug_warning(&mut self, message: impl Into<String>) {
340 self.debug_warning_inner(message.into());
341 }
342
343 #[inline]
344 #[deprecated(note = "Use debug_error! macro for lazy evaluation")]
345 #[allow(deprecated)]
346 pub fn debug_error(&mut self, message: impl Into<String>) {
347 self.debug_error_inner(message.into());
348 }
349
350 #[inline]
351 #[deprecated(note = "Use debug_log! macro for lazy evaluation")]
352 #[allow(deprecated)]
353 pub fn debug_log(&mut self, message: &str) {
354 self.debug_log_inner(message.to_string());
355 }
356
357 #[inline]
358 #[deprecated(note = "Use debug_box_props! macro for lazy evaluation")]
359 #[allow(deprecated)]
360 pub fn debug_box_props(&mut self, message: impl Into<String>) {
361 self.debug_box_props_inner(message.into());
362 }
363
364 #[inline]
365 #[deprecated(note = "Use debug_css_getter! macro for lazy evaluation")]
366 #[allow(deprecated)]
367 pub fn debug_css_getter(&mut self, message: impl Into<String>) {
368 self.debug_css_getter_inner(message.into());
369 }
370
371 #[inline]
372 #[deprecated(note = "Use debug_bfc_layout! macro for lazy evaluation")]
373 #[allow(deprecated)]
374 pub fn debug_bfc_layout(&mut self, message: impl Into<String>) {
375 self.debug_bfc_layout_inner(message.into());
376 }
377
378 #[inline]
379 #[deprecated(note = "Use debug_ifc_layout! macro for lazy evaluation")]
380 #[allow(deprecated)]
381 pub fn debug_ifc_layout(&mut self, message: impl Into<String>) {
382 self.debug_ifc_layout_inner(message.into());
383 }
384
385 #[inline]
386 #[deprecated(note = "Use debug_table_layout! macro for lazy evaluation")]
387 #[allow(deprecated)]
388 pub fn debug_table_layout(&mut self, message: impl Into<String>) {
389 self.debug_table_layout_inner(message.into());
390 }
391
392 #[inline]
393 #[deprecated(note = "Use debug_display_type! macro for lazy evaluation")]
394 #[allow(deprecated)]
395 pub fn debug_display_type(&mut self, message: impl Into<String>) {
396 self.debug_display_type_inner(message.into());
397 }
398}
399
400#[cfg(feature = "text_layout")]
402pub fn layout_document<T: ParsedFontTrait + Sync + 'static>(
403 cache: &mut LayoutCache,
404 text_cache: &mut TextLayoutCache,
405 new_dom: StyledDom,
406 viewport: LogicalRect,
407 font_manager: &crate::font_traits::FontManager<T>,
408 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
409 selections: &BTreeMap<DomId, SelectionState>,
410 text_selections: &BTreeMap<DomId, TextSelection>,
411 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
412 gpu_value_cache: Option<&azul_core::gpu::GpuValueCache>,
413 renderer_resources: &azul_core::resources::RendererResources,
414 id_namespace: azul_core::resources::IdNamespace,
415 dom_id: azul_core::dom::DomId,
416 cursor_is_visible: bool,
417 cursor_location: Option<(DomId, NodeId, TextCursor)>,
418 system_style: Option<std::sync::Arc<azul_css::system::SystemStyle>>,
419 get_system_time_fn: azul_core::task::GetSystemTimeCallback,
420) -> Result<DisplayList> {
421 crate::solver3::layout_tree::IfcId::reset_counter();
424
425 if let Some(msgs) = debug_messages.as_mut() {
426 msgs.push(LayoutDebugMessage::info(format!(
427 "[Layout] layout_document called - viewport: ({:.1}, {:.1}) size ({:.1}x{:.1})",
428 viewport.origin.x, viewport.origin.y, viewport.size.width, viewport.size.height
429 )));
430 msgs.push(LayoutDebugMessage::info(format!(
431 "[Layout] DOM has {} nodes",
432 new_dom.node_data.len()
433 )));
434 }
435
436 let mut counter_values = BTreeMap::new();
438 let mut ctx_temp = LayoutContext {
439 styled_dom: &new_dom,
440 font_manager,
441 selections,
442 text_selections,
443 debug_messages,
444 counters: &mut counter_values,
445 viewport_size: viewport.size,
446 fragmentation_context: None,
447 cursor_is_visible,
448 cursor_location: cursor_location.clone(),
449 cache_map: cache::LayoutCacheMap::default(), system_style: system_style.clone(),
451 get_system_time_fn,
452 };
453
454 let (mut new_tree, mut recon_result) =
456 cache::reconcile_and_invalidate(&mut ctx_temp, cache, viewport)?;
457
458 for &node_idx in &recon_result.intrinsic_dirty {
460 if let Some(node) = new_tree.get_mut(node_idx) {
461 node.taffy_cache.clear();
462 }
463 }
464
465 cache::compute_counters(&new_dom, &new_tree, &mut counter_values);
469
470 let mut cache_map = std::mem::take(&mut cache.cache_map);
474 cache_map.resize_to_tree(new_tree.nodes.len());
475 for &node_idx in &recon_result.intrinsic_dirty {
476 cache_map.mark_dirty(node_idx, &new_tree.nodes);
477 }
478 for &node_idx in &recon_result.layout_roots {
479 cache_map.mark_dirty(node_idx, &new_tree.nodes);
480 }
481
482 let mut ctx = LayoutContext {
484 styled_dom: &new_dom,
485 font_manager,
486 selections,
487 text_selections,
488 debug_messages,
489 counters: &mut counter_values,
490 viewport_size: viewport.size,
491 fragmentation_context: None,
492 cursor_is_visible,
493 cursor_location,
494 cache_map, system_style,
496 get_system_time_fn,
497 };
498
499 if recon_result.is_clean() {
501 ctx.debug_log("No changes, returning existing display list");
502 let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
503
504 let scroll_ids = if cache.scroll_ids.is_empty() {
506 use crate::window::LayoutWindow;
507 let (scroll_ids, scroll_id_to_node_id) =
508 LayoutWindow::compute_scroll_ids(tree, &new_dom);
509 cache.scroll_ids = scroll_ids.clone();
510 cache.scroll_id_to_node_id = scroll_id_to_node_id;
511 scroll_ids
512 } else {
513 cache.scroll_ids.clone()
514 };
515
516 return generate_display_list(
517 &mut ctx,
518 tree,
519 &cache.calculated_positions,
520 scroll_offsets,
521 &scroll_ids,
522 gpu_value_cache,
523 renderer_resources,
524 id_namespace,
525 dom_id,
526 );
527 }
528
529 let mut calculated_positions = cache.calculated_positions.clone();
531 let mut loop_count = 0;
532 loop {
533 loop_count += 1;
534 if loop_count > 10 {
535 break;
537 }
538
539 calculated_positions = cache.calculated_positions.clone();
540 let mut reflow_needed_for_scrollbars = false;
541
542 calculate_intrinsic_sizes(&mut ctx, &mut new_tree, &recon_result.intrinsic_dirty)?;
543
544 for &root_idx in &recon_result.layout_roots {
545 let (cb_pos, cb_size) = get_containing_block_for_node(
546 &new_tree,
547 &new_dom,
548 root_idx,
549 &calculated_positions,
550 viewport,
551 );
552
553 let root_node = &new_tree.nodes[root_idx];
558
559 let is_root_with_margin = root_node.parent.is_none()
560 && (root_node.box_props.margin.left != 0.0 || root_node.box_props.margin.top != 0.0);
561
562 let adjusted_cb_pos = if is_root_with_margin {
563 LogicalPosition::new(
564 cb_pos.x + root_node.box_props.margin.left,
565 cb_pos.y + root_node.box_props.margin.top,
566 )
567 } else {
568 cb_pos
569 };
570
571 if let Some(debug_msgs) = ctx.debug_messages.as_mut() {
573 let dom_name = root_node
574 .dom_node_id
575 .and_then(|id| new_dom.node_data.as_container().internal.get(id.index()))
576 .map(|n| format!("{:?}", n.node_type))
577 .unwrap_or_else(|| "Unknown".to_string());
578
579 debug_msgs.push(LayoutDebugMessage::new(
580 LayoutDebugMessageType::PositionCalculation,
581 format!(
582 "[LAYOUT ROOT {}] {} - CB pos=({:.2}, {:.2}), adjusted=({:.2}, {:.2}), \
583 CB size=({:.2}x{:.2}), viewport=({:.2}x{:.2}), margin=({:.2}, {:.2})",
584 root_idx,
585 dom_name,
586 cb_pos.x,
587 cb_pos.y,
588 adjusted_cb_pos.x,
589 adjusted_cb_pos.y,
590 cb_size.width,
591 cb_size.height,
592 viewport.size.width,
593 viewport.size.height,
594 root_node.box_props.margin.left,
595 root_node.box_props.margin.top
596 ),
597 ));
598 }
599
600 cache::calculate_layout_for_subtree(
601 &mut ctx,
602 &mut new_tree,
603 text_cache,
604 root_idx,
605 adjusted_cb_pos,
606 cb_size,
607 &mut calculated_positions,
608 &mut reflow_needed_for_scrollbars,
609 &mut cache.float_cache,
610 cache::ComputeMode::PerformLayout,
611 )?;
612
613 if !pos_contains(&calculated_positions, root_idx) {
621 let root_node = &new_tree.nodes[root_idx];
622
623 let root_position = LogicalPosition::new(
627 cb_pos.x + root_node.box_props.margin.left,
628 cb_pos.y + root_node.box_props.margin.top,
629 );
630
631 if let Some(debug_msgs) = ctx.debug_messages.as_mut() {
633 let dom_name = root_node
634 .dom_node_id
635 .and_then(|id| new_dom.node_data.as_container().internal.get(id.index()))
636 .map(|n| format!("{:?}", n.node_type))
637 .unwrap_or_else(|| "Unknown".to_string());
638
639 debug_msgs.push(LayoutDebugMessage::new(
640 LayoutDebugMessageType::PositionCalculation,
641 format!(
642 "[ROOT POSITION {}] {} - Inserting position=({:.2}, {:.2}) (viewport origin + margin), \
643 margin=({:.2}, {:.2}, {:.2}, {:.2})",
644 root_idx,
645 dom_name,
646 root_position.x,
647 root_position.y,
648 root_node.box_props.margin.top,
649 root_node.box_props.margin.right,
650 root_node.box_props.margin.bottom,
651 root_node.box_props.margin.left
652 ),
653 ));
654 }
655
656 pos_set(&mut calculated_positions, root_idx, root_position);
657 }
658 }
659
660 cache::reposition_clean_subtrees(
661 &new_dom,
662 &new_tree,
663 &recon_result.layout_roots,
664 &mut calculated_positions,
665 );
666
667 if reflow_needed_for_scrollbars {
668 ctx.debug_log(&format!(
669 "Scrollbars changed container size, starting full reflow (loop {})",
670 loop_count
671 ));
672 recon_result.layout_roots.clear();
673 recon_result.layout_roots.insert(new_tree.root);
674 recon_result.intrinsic_dirty = (0..new_tree.nodes.len()).collect();
675 continue;
676 }
677
678 break;
679 }
680
681 positioning::adjust_relative_positions(
689 &mut ctx,
690 &new_tree,
691 &mut calculated_positions,
692 viewport,
693 )?;
694
695 positioning::position_out_of_flow_elements(
700 &mut ctx,
701 &mut new_tree,
702 &mut calculated_positions,
703 viewport,
704 )?;
705
706 use crate::window::LayoutWindow;
709 let (scroll_ids, scroll_id_to_node_id) = LayoutWindow::compute_scroll_ids(&new_tree, &new_dom);
710
711 let display_list = generate_display_list(
713 &mut ctx,
714 &new_tree,
715 &calculated_positions,
716 scroll_offsets,
717 &scroll_ids,
718 gpu_value_cache,
719 renderer_resources,
720 id_namespace,
721 dom_id,
722 )?;
723
724 let cache_map_back = std::mem::take(&mut ctx.cache_map);
726
727 cache.tree = Some(new_tree);
728 cache.calculated_positions = calculated_positions;
729 cache.viewport = Some(viewport);
730 cache.scroll_ids = scroll_ids;
731 cache.scroll_id_to_node_id = scroll_id_to_node_id;
732 cache.counters = counter_values;
733 cache.cache_map = cache_map_back;
734
735 Ok(display_list)
736}
737
738fn get_containing_block_for_node(
740 tree: &LayoutTree,
741 styled_dom: &StyledDom,
742 node_idx: usize,
743 calculated_positions: &PositionVec,
744 viewport: LogicalRect,
745) -> (LogicalPosition, LogicalSize) {
746 if let Some(parent_idx) = tree.get(node_idx).and_then(|n| n.parent) {
747 if let Some(parent_node) = tree.get(parent_idx) {
748 let pos = calculated_positions
749 .get(parent_idx)
750 .copied()
751 .unwrap_or_default();
752 let size = parent_node.used_size.unwrap_or_default();
753 let content_pos = LogicalPosition::new(
756 pos.x + parent_node.box_props.border.left + parent_node.box_props.padding.left,
757 pos.y + parent_node.box_props.border.top + parent_node.box_props.padding.top,
758 );
759
760 if let Some(dom_id) = parent_node.dom_node_id {
761 let styled_node_state = &styled_dom
762 .styled_nodes
763 .as_container()
764 .get(dom_id)
765 .map(|n| &n.styled_node_state)
766 .cloned()
767 .unwrap_or_default();
768 let writing_mode =
769 get_writing_mode(styled_dom, dom_id, styled_node_state).unwrap_or_default();
770 let content_size = parent_node.box_props.inner_size(size, writing_mode);
771 return (content_pos, content_size);
772 }
773
774 return (content_pos, size);
775 }
776 }
777
778 (viewport.origin, viewport.size)
784}
785
786#[derive(Debug)]
787pub enum LayoutError {
788 InvalidTree,
789 SizingFailed,
790 PositioningFailed,
791 DisplayListFailed,
792 Text(crate::font_traits::LayoutError),
793}
794
795impl std::fmt::Display for LayoutError {
796 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
797 match self {
798 LayoutError::InvalidTree => write!(f, "Invalid layout tree"),
799 LayoutError::SizingFailed => write!(f, "Sizing calculation failed"),
800 LayoutError::PositioningFailed => write!(f, "Position calculation failed"),
801 LayoutError::DisplayListFailed => write!(f, "Display list generation failed"),
802 LayoutError::Text(e) => write!(f, "Text layout error: {:?}", e),
803 }
804 }
805}
806
807impl From<crate::font_traits::LayoutError> for LayoutError {
808 fn from(err: crate::font_traits::LayoutError) -> Self {
809 LayoutError::Text(err)
810 }
811}
812
813impl std::error::Error for LayoutError {}
814
815pub type Result<T> = std::result::Result<T, LayoutError>;