1pub mod cache;
6pub mod counters;
7pub mod display_list;
8pub mod fc;
9pub mod geometry;
10pub mod getters;
11pub mod layout_tree;
12pub mod paged_layout;
13pub mod pagination;
14pub mod positioning;
15pub mod scrollbar;
16pub mod sizing;
17pub mod taffy_bridge;
18
19#[macro_export]
21macro_rules! debug_info {
22 ($ctx:expr, $($arg:tt)*) => {
23 if $ctx.debug_messages.is_some() {
24 $ctx.debug_info_inner(format!($($arg)*));
25 }
26 };
27}
28
29#[macro_export]
31macro_rules! debug_warning {
32 ($ctx:expr, $($arg:tt)*) => {
33 if $ctx.debug_messages.is_some() {
34 $ctx.debug_warning_inner(format!($($arg)*));
35 }
36 };
37}
38
39#[macro_export]
41macro_rules! debug_error {
42 ($ctx:expr, $($arg:tt)*) => {
43 if $ctx.debug_messages.is_some() {
44 $ctx.debug_error_inner(format!($($arg)*));
45 }
46 };
47}
48
49#[macro_export]
51macro_rules! debug_log {
52 ($ctx:expr, $($arg:tt)*) => {
53 if $ctx.debug_messages.is_some() {
54 $ctx.debug_log_inner(format!($($arg)*));
55 }
56 };
57}
58
59#[macro_export]
61macro_rules! debug_box_props {
62 ($ctx:expr, $($arg:tt)*) => {
63 if $ctx.debug_messages.is_some() {
64 $ctx.debug_box_props_inner(format!($($arg)*));
65 }
66 };
67}
68
69#[macro_export]
71macro_rules! debug_css_getter {
72 ($ctx:expr, $($arg:tt)*) => {
73 if $ctx.debug_messages.is_some() {
74 $ctx.debug_css_getter_inner(format!($($arg)*));
75 }
76 };
77}
78
79#[macro_export]
81macro_rules! debug_bfc_layout {
82 ($ctx:expr, $($arg:tt)*) => {
83 if $ctx.debug_messages.is_some() {
84 $ctx.debug_bfc_layout_inner(format!($($arg)*));
85 }
86 };
87}
88
89#[macro_export]
91macro_rules! debug_ifc_layout {
92 ($ctx:expr, $($arg:tt)*) => {
93 if $ctx.debug_messages.is_some() {
94 $ctx.debug_ifc_layout_inner(format!($($arg)*));
95 }
96 };
97}
98
99#[macro_export]
101macro_rules! debug_table_layout {
102 ($ctx:expr, $($arg:tt)*) => {
103 if $ctx.debug_messages.is_some() {
104 $ctx.debug_table_layout_inner(format!($($arg)*));
105 }
106 };
107}
108
109#[macro_export]
111macro_rules! debug_display_type {
112 ($ctx:expr, $($arg:tt)*) => {
113 if $ctx.debug_messages.is_some() {
114 $ctx.debug_display_type_inner(format!($($arg)*));
115 }
116 };
117}
118
119use std::{collections::BTreeMap, sync::Arc};
126
127use azul_core::{
128 dom::{DomId, NodeId},
129 geom::{LogicalPosition, LogicalRect, LogicalSize},
130 hit_test::{DocumentId, ScrollPosition},
131 resources::RendererResources,
132 selection::{SelectionState, TextCursor, TextSelection},
133 styled_dom::StyledDom,
134};
135use azul_css::{
136 props::property::{CssProperty, CssPropertyCategory},
137 LayoutDebugMessage, LayoutDebugMessageType,
138};
139
140use self::{
141 display_list::generate_display_list,
142 geometry::IntrinsicSizes,
143 getters::get_writing_mode,
144 layout_tree::{generate_layout_tree, LayoutTree},
145 sizing::calculate_intrinsic_sizes,
146};
147#[cfg(feature = "text_layout")]
148pub use crate::font_traits::TextLayoutCache;
149use crate::{
150 font_traits::ParsedFontTrait,
151 solver3::{
152 cache::LayoutCache,
153 display_list::DisplayList,
154 fc::{check_scrollbar_necessity, LayoutConstraints, LayoutResult},
155 layout_tree::DirtyFlag,
156 },
157};
158
159pub type NodeHashMap = BTreeMap<usize, u64>;
161
162pub struct LayoutContext<'a, T: ParsedFontTrait> {
164 pub styled_dom: &'a StyledDom,
165 #[cfg(feature = "text_layout")]
166 pub font_manager: &'a crate::font_traits::FontManager<T>,
167 #[cfg(not(feature = "text_layout"))]
168 pub font_manager: core::marker::PhantomData<&'a T>,
169 pub selections: &'a BTreeMap<DomId, SelectionState>,
171 pub text_selections: &'a BTreeMap<DomId, TextSelection>,
173 pub debug_messages: &'a mut Option<Vec<LayoutDebugMessage>>,
174 pub counters: &'a mut BTreeMap<(usize, String), i32>,
175 pub viewport_size: LogicalSize,
176 pub fragmentation_context: Option<&'a mut crate::paged::FragmentationContext>,
179 pub cursor_is_visible: bool,
183 pub cursor_location: Option<(DomId, NodeId, TextCursor)>,
187}
188
189impl<'a, T: ParsedFontTrait> LayoutContext<'a, T> {
190 #[inline]
192 pub fn has_debug(&self) -> bool {
193 self.debug_messages.is_some()
194 }
195
196 #[inline]
198 pub fn debug_log_inner(&mut self, message: String) {
199 if let Some(messages) = self.debug_messages.as_mut() {
200 messages.push(LayoutDebugMessage {
201 message: message.into(),
202 location: "solver3".into(),
203 message_type: Default::default(),
204 });
205 }
206 }
207
208 #[inline]
210 pub fn debug_info_inner(&mut self, message: String) {
211 if let Some(messages) = self.debug_messages.as_mut() {
212 messages.push(LayoutDebugMessage::info(message));
213 }
214 }
215
216 #[inline]
218 pub fn debug_warning_inner(&mut self, message: String) {
219 if let Some(messages) = self.debug_messages.as_mut() {
220 messages.push(LayoutDebugMessage::warning(message));
221 }
222 }
223
224 #[inline]
226 pub fn debug_error_inner(&mut self, message: String) {
227 if let Some(messages) = self.debug_messages.as_mut() {
228 messages.push(LayoutDebugMessage::error(message));
229 }
230 }
231
232 #[inline]
234 pub fn debug_box_props_inner(&mut self, message: String) {
235 if let Some(messages) = self.debug_messages.as_mut() {
236 messages.push(LayoutDebugMessage::box_props(message));
237 }
238 }
239
240 #[inline]
242 pub fn debug_css_getter_inner(&mut self, message: String) {
243 if let Some(messages) = self.debug_messages.as_mut() {
244 messages.push(LayoutDebugMessage::css_getter(message));
245 }
246 }
247
248 #[inline]
250 pub fn debug_bfc_layout_inner(&mut self, message: String) {
251 if let Some(messages) = self.debug_messages.as_mut() {
252 messages.push(LayoutDebugMessage::bfc_layout(message));
253 }
254 }
255
256 #[inline]
258 pub fn debug_ifc_layout_inner(&mut self, message: String) {
259 if let Some(messages) = self.debug_messages.as_mut() {
260 messages.push(LayoutDebugMessage::ifc_layout(message));
261 }
262 }
263
264 #[inline]
266 pub fn debug_table_layout_inner(&mut self, message: String) {
267 if let Some(messages) = self.debug_messages.as_mut() {
268 messages.push(LayoutDebugMessage::table_layout(message));
269 }
270 }
271
272 #[inline]
274 pub fn debug_display_type_inner(&mut self, message: String) {
275 if let Some(messages) = self.debug_messages.as_mut() {
276 messages.push(LayoutDebugMessage::display_type(message));
277 }
278 }
279
280 #[inline]
284 #[deprecated(note = "Use debug_info! macro for lazy evaluation")]
285 #[allow(deprecated)]
286 pub fn debug_info(&mut self, message: impl Into<String>) {
287 self.debug_info_inner(message.into());
288 }
289
290 #[inline]
291 #[deprecated(note = "Use debug_warning! macro for lazy evaluation")]
292 #[allow(deprecated)]
293 pub fn debug_warning(&mut self, message: impl Into<String>) {
294 self.debug_warning_inner(message.into());
295 }
296
297 #[inline]
298 #[deprecated(note = "Use debug_error! macro for lazy evaluation")]
299 #[allow(deprecated)]
300 pub fn debug_error(&mut self, message: impl Into<String>) {
301 self.debug_error_inner(message.into());
302 }
303
304 #[inline]
305 #[deprecated(note = "Use debug_log! macro for lazy evaluation")]
306 #[allow(deprecated)]
307 pub fn debug_log(&mut self, message: &str) {
308 self.debug_log_inner(message.to_string());
309 }
310
311 #[inline]
312 #[deprecated(note = "Use debug_box_props! macro for lazy evaluation")]
313 #[allow(deprecated)]
314 pub fn debug_box_props(&mut self, message: impl Into<String>) {
315 self.debug_box_props_inner(message.into());
316 }
317
318 #[inline]
319 #[deprecated(note = "Use debug_css_getter! macro for lazy evaluation")]
320 #[allow(deprecated)]
321 pub fn debug_css_getter(&mut self, message: impl Into<String>) {
322 self.debug_css_getter_inner(message.into());
323 }
324
325 #[inline]
326 #[deprecated(note = "Use debug_bfc_layout! macro for lazy evaluation")]
327 #[allow(deprecated)]
328 pub fn debug_bfc_layout(&mut self, message: impl Into<String>) {
329 self.debug_bfc_layout_inner(message.into());
330 }
331
332 #[inline]
333 #[deprecated(note = "Use debug_ifc_layout! macro for lazy evaluation")]
334 #[allow(deprecated)]
335 pub fn debug_ifc_layout(&mut self, message: impl Into<String>) {
336 self.debug_ifc_layout_inner(message.into());
337 }
338
339 #[inline]
340 #[deprecated(note = "Use debug_table_layout! macro for lazy evaluation")]
341 #[allow(deprecated)]
342 pub fn debug_table_layout(&mut self, message: impl Into<String>) {
343 self.debug_table_layout_inner(message.into());
344 }
345
346 #[inline]
347 #[deprecated(note = "Use debug_display_type! macro for lazy evaluation")]
348 #[allow(deprecated)]
349 pub fn debug_display_type(&mut self, message: impl Into<String>) {
350 self.debug_display_type_inner(message.into());
351 }
352}
353
354#[cfg(feature = "text_layout")]
356pub fn layout_document<T: ParsedFontTrait + Sync + 'static>(
357 cache: &mut LayoutCache,
358 text_cache: &mut TextLayoutCache,
359 new_dom: StyledDom,
360 viewport: LogicalRect,
361 font_manager: &crate::font_traits::FontManager<T>,
362 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
363 selections: &BTreeMap<DomId, SelectionState>,
364 text_selections: &BTreeMap<DomId, TextSelection>,
365 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
366 gpu_value_cache: Option<&azul_core::gpu::GpuValueCache>,
367 renderer_resources: &azul_core::resources::RendererResources,
368 id_namespace: azul_core::resources::IdNamespace,
369 dom_id: azul_core::dom::DomId,
370 cursor_is_visible: bool,
371 cursor_location: Option<(DomId, NodeId, TextCursor)>,
372) -> Result<DisplayList> {
373 crate::solver3::layout_tree::IfcId::reset_counter();
376
377 if let Some(msgs) = debug_messages.as_mut() {
378 msgs.push(LayoutDebugMessage::info(format!(
379 "[Layout] layout_document called - viewport: ({:.1}, {:.1}) size ({:.1}x{:.1})",
380 viewport.origin.x, viewport.origin.y, viewport.size.width, viewport.size.height
381 )));
382 msgs.push(LayoutDebugMessage::info(format!(
383 "[Layout] DOM has {} nodes",
384 new_dom.node_data.len()
385 )));
386 }
387
388 let mut counter_values = BTreeMap::new();
390 let mut ctx_temp = LayoutContext {
391 styled_dom: &new_dom,
392 font_manager,
393 selections,
394 text_selections,
395 debug_messages,
396 counters: &mut counter_values,
397 viewport_size: viewport.size,
398 fragmentation_context: None,
399 cursor_is_visible,
400 cursor_location: cursor_location.clone(),
401 };
402
403 let (mut new_tree, mut recon_result) =
405 cache::reconcile_and_invalidate(&mut ctx_temp, cache, viewport)?;
406
407 for &node_idx in &recon_result.intrinsic_dirty {
409 if let Some(node) = new_tree.get_mut(node_idx) {
410 node.taffy_cache.clear();
411 }
412 }
413
414 cache::compute_counters(&new_dom, &new_tree, &mut counter_values);
418
419 let mut ctx = LayoutContext {
421 styled_dom: &new_dom,
422 font_manager,
423 selections,
424 text_selections,
425 debug_messages,
426 counters: &mut counter_values,
427 viewport_size: viewport.size,
428 fragmentation_context: None,
429 cursor_is_visible,
430 cursor_location,
431 };
432
433 if recon_result.is_clean() {
435 ctx.debug_log("No changes, returning existing display list");
436 let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
437
438 let scroll_ids = if cache.scroll_ids.is_empty() {
440 use crate::window::LayoutWindow;
441 let (scroll_ids, scroll_id_to_node_id) =
442 LayoutWindow::compute_scroll_ids(tree, &new_dom);
443 cache.scroll_ids = scroll_ids.clone();
444 cache.scroll_id_to_node_id = scroll_id_to_node_id;
445 scroll_ids
446 } else {
447 cache.scroll_ids.clone()
448 };
449
450 return generate_display_list(
451 &mut ctx,
452 tree,
453 &cache.calculated_positions,
454 scroll_offsets,
455 &scroll_ids,
456 gpu_value_cache,
457 renderer_resources,
458 id_namespace,
459 dom_id,
460 );
461 }
462
463 let mut calculated_positions = cache.calculated_positions.clone();
465 let mut loop_count = 0;
466 loop {
467 loop_count += 1;
468 if loop_count > 10 {
469 break;
471 }
472
473 calculated_positions = cache.calculated_positions.clone();
474 let mut reflow_needed_for_scrollbars = false;
475
476 calculate_intrinsic_sizes(&mut ctx, &mut new_tree, &recon_result.intrinsic_dirty)?;
477
478 for &root_idx in &recon_result.layout_roots {
479 let (cb_pos, cb_size) = get_containing_block_for_node(
480 &new_tree,
481 &new_dom,
482 root_idx,
483 &calculated_positions,
484 viewport,
485 );
486
487 let root_node = &new_tree.nodes[root_idx];
492 let is_root_with_margin = root_node.parent.is_none()
493 && (root_node.box_props.margin.left != 0.0 || root_node.box_props.margin.top != 0.0);
494
495 let adjusted_cb_pos = if is_root_with_margin {
496 LogicalPosition::new(
497 cb_pos.x + root_node.box_props.margin.left,
498 cb_pos.y + root_node.box_props.margin.top,
499 )
500 } else {
501 cb_pos
502 };
503
504 if let Some(debug_msgs) = ctx.debug_messages.as_mut() {
506 let dom_name = root_node
507 .dom_node_id
508 .and_then(|id| new_dom.node_data.as_container().internal.get(id.index()))
509 .map(|n| format!("{:?}", n.node_type))
510 .unwrap_or_else(|| "Unknown".to_string());
511
512 debug_msgs.push(LayoutDebugMessage::new(
513 LayoutDebugMessageType::PositionCalculation,
514 format!(
515 "[LAYOUT ROOT {}] {} - CB pos=({:.2}, {:.2}), adjusted=({:.2}, {:.2}), \
516 CB size=({:.2}x{:.2}), viewport=({:.2}x{:.2}), margin=({:.2}, {:.2})",
517 root_idx,
518 dom_name,
519 cb_pos.x,
520 cb_pos.y,
521 adjusted_cb_pos.x,
522 adjusted_cb_pos.y,
523 cb_size.width,
524 cb_size.height,
525 viewport.size.width,
526 viewport.size.height,
527 root_node.box_props.margin.left,
528 root_node.box_props.margin.top
529 ),
530 ));
531 }
532
533 cache::calculate_layout_for_subtree(
534 &mut ctx,
535 &mut new_tree,
536 text_cache,
537 root_idx,
538 adjusted_cb_pos,
539 cb_size,
540 &mut calculated_positions,
541 &mut reflow_needed_for_scrollbars,
542 &mut cache.float_cache,
543 )?;
544
545 if !calculated_positions.contains_key(&root_idx) {
553 let root_node = &new_tree.nodes[root_idx];
554
555 let root_position = LogicalPosition::new(
559 cb_pos.x + root_node.box_props.margin.left,
560 cb_pos.y + root_node.box_props.margin.top,
561 );
562
563 if let Some(debug_msgs) = ctx.debug_messages.as_mut() {
565 let dom_name = root_node
566 .dom_node_id
567 .and_then(|id| new_dom.node_data.as_container().internal.get(id.index()))
568 .map(|n| format!("{:?}", n.node_type))
569 .unwrap_or_else(|| "Unknown".to_string());
570
571 debug_msgs.push(LayoutDebugMessage::new(
572 LayoutDebugMessageType::PositionCalculation,
573 format!(
574 "[ROOT POSITION {}] {} - Inserting position=({:.2}, {:.2}) (viewport origin + margin), \
575 margin=({:.2}, {:.2}, {:.2}, {:.2})",
576 root_idx,
577 dom_name,
578 root_position.x,
579 root_position.y,
580 root_node.box_props.margin.top,
581 root_node.box_props.margin.right,
582 root_node.box_props.margin.bottom,
583 root_node.box_props.margin.left
584 ),
585 ));
586 }
587
588 calculated_positions.insert(root_idx, root_position);
589 }
590 }
591
592 cache::reposition_clean_subtrees(
593 &new_dom,
594 &new_tree,
595 &recon_result.layout_roots,
596 &mut calculated_positions,
597 );
598
599 if reflow_needed_for_scrollbars {
600 ctx.debug_log(&format!(
601 "Scrollbars changed container size, starting full reflow (loop {})",
602 loop_count
603 ));
604 recon_result.layout_roots.clear();
605 recon_result.layout_roots.insert(new_tree.root);
606 recon_result.intrinsic_dirty = (0..new_tree.nodes.len()).collect();
607 continue;
608 }
609
610 break;
611 }
612
613 positioning::adjust_relative_positions(
621 &mut ctx,
622 &new_tree,
623 &mut calculated_positions,
624 viewport,
625 )?;
626
627 positioning::position_out_of_flow_elements(
632 &mut ctx,
633 &mut new_tree,
634 &mut calculated_positions,
635 viewport,
636 )?;
637
638 use crate::window::LayoutWindow;
641 let (scroll_ids, scroll_id_to_node_id) = LayoutWindow::compute_scroll_ids(&new_tree, &new_dom);
642
643 let display_list = generate_display_list(
645 &mut ctx,
646 &new_tree,
647 &calculated_positions,
648 scroll_offsets,
649 &scroll_ids,
650 gpu_value_cache,
651 renderer_resources,
652 id_namespace,
653 dom_id,
654 )?;
655
656 cache.tree = Some(new_tree);
657 cache.calculated_positions = calculated_positions;
658 cache.viewport = Some(viewport);
659 cache.scroll_ids = scroll_ids;
660 cache.scroll_id_to_node_id = scroll_id_to_node_id;
661 cache.counters = counter_values;
662
663 Ok(display_list)
664}
665
666fn get_containing_block_for_node(
668 tree: &LayoutTree,
669 styled_dom: &StyledDom,
670 node_idx: usize,
671 calculated_positions: &BTreeMap<usize, LogicalPosition>,
672 viewport: LogicalRect,
673) -> (LogicalPosition, LogicalSize) {
674 if let Some(parent_idx) = tree.get(node_idx).and_then(|n| n.parent) {
675 if let Some(parent_node) = tree.get(parent_idx) {
676 let pos = calculated_positions
677 .get(&parent_idx)
678 .copied()
679 .unwrap_or_default();
680 let size = parent_node.used_size.unwrap_or_default();
681 let content_pos = LogicalPosition::new(
684 pos.x + parent_node.box_props.border.left + parent_node.box_props.padding.left,
685 pos.y + parent_node.box_props.border.top + parent_node.box_props.padding.top,
686 );
687
688 if let Some(dom_id) = parent_node.dom_node_id {
689 let styled_node_state = &styled_dom
690 .styled_nodes
691 .as_container()
692 .get(dom_id)
693 .map(|n| &n.styled_node_state)
694 .cloned()
695 .unwrap_or_default();
696 let writing_mode =
697 get_writing_mode(styled_dom, dom_id, styled_node_state).unwrap_or_default();
698 let content_size = parent_node.box_props.inner_size(size, writing_mode);
699 return (content_pos, content_size);
700 }
701
702 return (content_pos, size);
703 }
704 }
705 (viewport.origin, viewport.size)
706}
707
708#[derive(Debug)]
709pub enum LayoutError {
710 InvalidTree,
711 SizingFailed,
712 PositioningFailed,
713 DisplayListFailed,
714 Text(crate::font_traits::LayoutError),
715}
716
717impl std::fmt::Display for LayoutError {
718 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
719 match self {
720 LayoutError::InvalidTree => write!(f, "Invalid layout tree"),
721 LayoutError::SizingFailed => write!(f, "Sizing calculation failed"),
722 LayoutError::PositioningFailed => write!(f, "Position calculation failed"),
723 LayoutError::DisplayListFailed => write!(f, "Display list generation failed"),
724 LayoutError::Text(e) => write!(f, "Text layout error: {:?}", e),
725 }
726 }
727}
728
729impl From<crate::font_traits::LayoutError> for LayoutError {
730 fn from(err: crate::font_traits::LayoutError) -> Self {
731 LayoutError::Text(err)
732 }
733}
734
735impl std::error::Error for LayoutError {}
736
737pub type Result<T> = std::result::Result<T, LayoutError>;