pub mod cache;
pub mod calc;
pub mod counters;
pub mod display_list;
pub mod fc;
pub mod geometry;
pub mod getters;
pub mod layout_tree;
pub mod paged_layout;
pub mod pagination;
pub mod positioning;
pub mod scrollbar;
pub mod sizing;
pub mod taffy_bridge;
#[macro_export]
macro_rules! debug_info {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_info_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_warning {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_warning_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_error {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_error_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_log {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_log_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_box_props {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_box_props_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_css_getter {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_css_getter_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_bfc_layout {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_bfc_layout_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_ifc_layout {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_ifc_layout_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_table_layout {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_table_layout_inner(format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug_display_type {
($ctx:expr, $($arg:tt)*) => {
if $ctx.debug_messages.is_some() {
$ctx.debug_display_type_inner(format!($($arg)*));
}
};
}
use std::{collections::BTreeMap, sync::Arc};
use azul_core::{
dom::{DomId, NodeId},
geom::{LogicalPosition, LogicalRect, LogicalSize},
hit_test::{DocumentId, ScrollPosition},
resources::RendererResources,
selection::{SelectionState, TextCursor, TextSelection},
styled_dom::StyledDom,
};
pub const POSITION_UNSET: LogicalPosition = LogicalPosition { x: f32::MIN, y: f32::MIN };
pub type PositionVec = Vec<LogicalPosition>;
#[inline]
pub fn new_position_vec(num_nodes: usize) -> PositionVec {
vec![POSITION_UNSET; num_nodes]
}
#[inline(always)]
pub fn pos_get(positions: &PositionVec, idx: usize) -> Option<LogicalPosition> {
positions.get(idx).copied().filter(|p| p.x != f32::MIN)
}
#[inline(always)]
pub fn pos_set(positions: &mut PositionVec, idx: usize, pos: LogicalPosition) {
if idx >= positions.len() {
positions.resize(idx + 1, POSITION_UNSET);
}
positions[idx] = pos;
}
#[inline(always)]
pub fn pos_contains(positions: &PositionVec, idx: usize) -> bool {
positions.get(idx).map_or(false, |p| p.x != f32::MIN)
}
use azul_css::{
props::property::{CssProperty, CssPropertyCategory},
LayoutDebugMessage, LayoutDebugMessageType,
};
use self::{
display_list::generate_display_list,
geometry::IntrinsicSizes,
getters::get_writing_mode,
layout_tree::{generate_layout_tree, LayoutTree},
sizing::calculate_intrinsic_sizes,
};
#[cfg(feature = "text_layout")]
pub use crate::font_traits::TextLayoutCache;
use crate::{
font_traits::ParsedFontTrait,
solver3::{
cache::LayoutCache,
display_list::DisplayList,
fc::{LayoutConstraints, LayoutResult},
layout_tree::DirtyFlag,
},
};
pub type NodeHashMap = BTreeMap<usize, u64>;
pub struct LayoutContext<'a, T: ParsedFontTrait> {
pub styled_dom: &'a StyledDom,
#[cfg(feature = "text_layout")]
pub font_manager: &'a crate::font_traits::FontManager<T>,
#[cfg(not(feature = "text_layout"))]
pub font_manager: core::marker::PhantomData<&'a T>,
pub selections: &'a BTreeMap<DomId, SelectionState>,
pub text_selections: &'a BTreeMap<DomId, TextSelection>,
pub debug_messages: &'a mut Option<Vec<LayoutDebugMessage>>,
pub counters: &'a mut BTreeMap<(usize, String), i32>,
pub viewport_size: LogicalSize,
pub fragmentation_context: Option<&'a mut crate::paged::FragmentationContext>,
pub cursor_is_visible: bool,
pub cursor_location: Option<(DomId, NodeId, TextCursor)>,
pub cache_map: cache::LayoutCacheMap,
pub system_style: Option<std::sync::Arc<azul_css::system::SystemStyle>>,
pub get_system_time_fn: azul_core::task::GetSystemTimeCallback,
}
impl<'a, T: ParsedFontTrait> LayoutContext<'a, T> {
#[inline]
pub fn has_debug(&self) -> bool {
self.debug_messages.is_some()
}
#[inline]
pub fn debug_log_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage {
message: message.into(),
location: "solver3".into(),
message_type: Default::default(),
});
}
}
#[inline]
pub fn debug_info_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::info(message));
}
}
#[inline]
pub fn debug_warning_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::warning(message));
}
}
#[inline]
pub fn debug_error_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::error(message));
}
}
#[inline]
pub fn debug_box_props_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::box_props(message));
}
}
#[inline]
pub fn debug_css_getter_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::css_getter(message));
}
}
#[inline]
pub fn debug_bfc_layout_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::bfc_layout(message));
}
}
#[inline]
pub fn debug_ifc_layout_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::ifc_layout(message));
}
}
#[inline]
pub fn debug_table_layout_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::table_layout(message));
}
}
#[inline]
pub fn debug_display_type_inner(&mut self, message: String) {
if let Some(messages) = self.debug_messages.as_mut() {
messages.push(LayoutDebugMessage::display_type(message));
}
}
#[inline]
#[deprecated(note = "Use debug_info! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_info(&mut self, message: impl Into<String>) {
self.debug_info_inner(message.into());
}
#[inline]
#[deprecated(note = "Use debug_warning! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_warning(&mut self, message: impl Into<String>) {
self.debug_warning_inner(message.into());
}
#[inline]
#[deprecated(note = "Use debug_error! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_error(&mut self, message: impl Into<String>) {
self.debug_error_inner(message.into());
}
#[inline]
#[deprecated(note = "Use debug_log! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_log(&mut self, message: &str) {
self.debug_log_inner(message.to_string());
}
#[inline]
#[deprecated(note = "Use debug_box_props! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_box_props(&mut self, message: impl Into<String>) {
self.debug_box_props_inner(message.into());
}
#[inline]
#[deprecated(note = "Use debug_css_getter! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_css_getter(&mut self, message: impl Into<String>) {
self.debug_css_getter_inner(message.into());
}
#[inline]
#[deprecated(note = "Use debug_bfc_layout! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_bfc_layout(&mut self, message: impl Into<String>) {
self.debug_bfc_layout_inner(message.into());
}
#[inline]
#[deprecated(note = "Use debug_ifc_layout! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_ifc_layout(&mut self, message: impl Into<String>) {
self.debug_ifc_layout_inner(message.into());
}
#[inline]
#[deprecated(note = "Use debug_table_layout! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_table_layout(&mut self, message: impl Into<String>) {
self.debug_table_layout_inner(message.into());
}
#[inline]
#[deprecated(note = "Use debug_display_type! macro for lazy evaluation")]
#[allow(deprecated)]
pub fn debug_display_type(&mut self, message: impl Into<String>) {
self.debug_display_type_inner(message.into());
}
}
#[cfg(feature = "text_layout")]
pub fn layout_document<T: ParsedFontTrait + Sync + 'static>(
cache: &mut LayoutCache,
text_cache: &mut TextLayoutCache,
new_dom: StyledDom,
viewport: LogicalRect,
font_manager: &crate::font_traits::FontManager<T>,
scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
selections: &BTreeMap<DomId, SelectionState>,
text_selections: &BTreeMap<DomId, TextSelection>,
debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
gpu_value_cache: Option<&azul_core::gpu::GpuValueCache>,
renderer_resources: &azul_core::resources::RendererResources,
id_namespace: azul_core::resources::IdNamespace,
dom_id: azul_core::dom::DomId,
cursor_is_visible: bool,
cursor_location: Option<(DomId, NodeId, TextCursor)>,
system_style: Option<std::sync::Arc<azul_css::system::SystemStyle>>,
get_system_time_fn: azul_core::task::GetSystemTimeCallback,
) -> Result<DisplayList> {
crate::solver3::layout_tree::IfcId::reset_counter();
if let Some(msgs) = debug_messages.as_mut() {
msgs.push(LayoutDebugMessage::info(format!(
"[Layout] layout_document called - viewport: ({:.1}, {:.1}) size ({:.1}x{:.1})",
viewport.origin.x, viewport.origin.y, viewport.size.width, viewport.size.height
)));
msgs.push(LayoutDebugMessage::info(format!(
"[Layout] DOM has {} nodes",
new_dom.node_data.len()
)));
}
let mut counter_values = BTreeMap::new();
let mut ctx_temp = LayoutContext {
styled_dom: &new_dom,
font_manager,
selections,
text_selections,
debug_messages,
counters: &mut counter_values,
viewport_size: viewport.size,
fragmentation_context: None,
cursor_is_visible,
cursor_location: cursor_location.clone(),
cache_map: cache::LayoutCacheMap::default(), system_style: system_style.clone(),
get_system_time_fn,
};
let (mut new_tree, mut recon_result) =
cache::reconcile_and_invalidate(&mut ctx_temp, cache, viewport)?;
for &node_idx in &recon_result.intrinsic_dirty {
if let Some(node) = new_tree.get_mut(node_idx) {
node.taffy_cache.clear();
}
}
cache::compute_counters(&new_dom, &new_tree, &mut counter_values);
let mut cache_map = std::mem::take(&mut cache.cache_map);
cache_map.resize_to_tree(new_tree.nodes.len());
for &node_idx in &recon_result.intrinsic_dirty {
cache_map.mark_dirty(node_idx, &new_tree.nodes);
}
for &node_idx in &recon_result.layout_roots {
cache_map.mark_dirty(node_idx, &new_tree.nodes);
}
let mut ctx = LayoutContext {
styled_dom: &new_dom,
font_manager,
selections,
text_selections,
debug_messages,
counters: &mut counter_values,
viewport_size: viewport.size,
fragmentation_context: None,
cursor_is_visible,
cursor_location,
cache_map, system_style,
get_system_time_fn,
};
if recon_result.is_clean() {
ctx.debug_log("No changes, returning existing display list");
let tree = cache.tree.as_ref().ok_or(LayoutError::InvalidTree)?;
let scroll_ids = if cache.scroll_ids.is_empty() {
use crate::window::LayoutWindow;
let (scroll_ids, scroll_id_to_node_id) =
LayoutWindow::compute_scroll_ids(tree, &new_dom);
cache.scroll_ids = scroll_ids.clone();
cache.scroll_id_to_node_id = scroll_id_to_node_id;
scroll_ids
} else {
cache.scroll_ids.clone()
};
return generate_display_list(
&mut ctx,
tree,
&cache.calculated_positions,
scroll_offsets,
&scroll_ids,
gpu_value_cache,
renderer_resources,
id_namespace,
dom_id,
);
}
let mut calculated_positions = cache.calculated_positions.clone();
let mut loop_count = 0;
loop {
loop_count += 1;
if loop_count > 10 {
break;
}
calculated_positions = cache.calculated_positions.clone();
let mut reflow_needed_for_scrollbars = false;
calculate_intrinsic_sizes(&mut ctx, &mut new_tree, &recon_result.intrinsic_dirty)?;
for &root_idx in &recon_result.layout_roots {
let (cb_pos, cb_size) = get_containing_block_for_node(
&new_tree,
&new_dom,
root_idx,
&calculated_positions,
viewport,
);
let root_node = &new_tree.nodes[root_idx];
let is_root_with_margin = root_node.parent.is_none()
&& (root_node.box_props.margin.left != 0.0 || root_node.box_props.margin.top != 0.0);
let adjusted_cb_pos = if is_root_with_margin {
LogicalPosition::new(
cb_pos.x + root_node.box_props.margin.left,
cb_pos.y + root_node.box_props.margin.top,
)
} else {
cb_pos
};
if let Some(debug_msgs) = ctx.debug_messages.as_mut() {
let dom_name = root_node
.dom_node_id
.and_then(|id| new_dom.node_data.as_container().internal.get(id.index()))
.map(|n| format!("{:?}", n.node_type))
.unwrap_or_else(|| "Unknown".to_string());
debug_msgs.push(LayoutDebugMessage::new(
LayoutDebugMessageType::PositionCalculation,
format!(
"[LAYOUT ROOT {}] {} - CB pos=({:.2}, {:.2}), adjusted=({:.2}, {:.2}), \
CB size=({:.2}x{:.2}), viewport=({:.2}x{:.2}), margin=({:.2}, {:.2})",
root_idx,
dom_name,
cb_pos.x,
cb_pos.y,
adjusted_cb_pos.x,
adjusted_cb_pos.y,
cb_size.width,
cb_size.height,
viewport.size.width,
viewport.size.height,
root_node.box_props.margin.left,
root_node.box_props.margin.top
),
));
}
cache::calculate_layout_for_subtree(
&mut ctx,
&mut new_tree,
text_cache,
root_idx,
adjusted_cb_pos,
cb_size,
&mut calculated_positions,
&mut reflow_needed_for_scrollbars,
&mut cache.float_cache,
cache::ComputeMode::PerformLayout,
)?;
if !pos_contains(&calculated_positions, root_idx) {
let root_node = &new_tree.nodes[root_idx];
let root_position = LogicalPosition::new(
cb_pos.x + root_node.box_props.margin.left,
cb_pos.y + root_node.box_props.margin.top,
);
if let Some(debug_msgs) = ctx.debug_messages.as_mut() {
let dom_name = root_node
.dom_node_id
.and_then(|id| new_dom.node_data.as_container().internal.get(id.index()))
.map(|n| format!("{:?}", n.node_type))
.unwrap_or_else(|| "Unknown".to_string());
debug_msgs.push(LayoutDebugMessage::new(
LayoutDebugMessageType::PositionCalculation,
format!(
"[ROOT POSITION {}] {} - Inserting position=({:.2}, {:.2}) (viewport origin + margin), \
margin=({:.2}, {:.2}, {:.2}, {:.2})",
root_idx,
dom_name,
root_position.x,
root_position.y,
root_node.box_props.margin.top,
root_node.box_props.margin.right,
root_node.box_props.margin.bottom,
root_node.box_props.margin.left
),
));
}
pos_set(&mut calculated_positions, root_idx, root_position);
}
}
cache::reposition_clean_subtrees(
&new_dom,
&new_tree,
&recon_result.layout_roots,
&mut calculated_positions,
);
if reflow_needed_for_scrollbars {
ctx.debug_log(&format!(
"Scrollbars changed container size, starting full reflow (loop {})",
loop_count
));
recon_result.layout_roots.clear();
recon_result.layout_roots.insert(new_tree.root);
recon_result.intrinsic_dirty = (0..new_tree.nodes.len()).collect();
continue;
}
break;
}
positioning::adjust_relative_positions(
&mut ctx,
&new_tree,
&mut calculated_positions,
viewport,
)?;
positioning::position_out_of_flow_elements(
&mut ctx,
&mut new_tree,
&mut calculated_positions,
viewport,
)?;
use crate::window::LayoutWindow;
let (scroll_ids, scroll_id_to_node_id) = LayoutWindow::compute_scroll_ids(&new_tree, &new_dom);
let display_list = generate_display_list(
&mut ctx,
&new_tree,
&calculated_positions,
scroll_offsets,
&scroll_ids,
gpu_value_cache,
renderer_resources,
id_namespace,
dom_id,
)?;
let cache_map_back = std::mem::take(&mut ctx.cache_map);
cache.tree = Some(new_tree);
cache.calculated_positions = calculated_positions;
cache.viewport = Some(viewport);
cache.scroll_ids = scroll_ids;
cache.scroll_id_to_node_id = scroll_id_to_node_id;
cache.counters = counter_values;
cache.cache_map = cache_map_back;
Ok(display_list)
}
fn get_containing_block_for_node(
tree: &LayoutTree,
styled_dom: &StyledDom,
node_idx: usize,
calculated_positions: &PositionVec,
viewport: LogicalRect,
) -> (LogicalPosition, LogicalSize) {
if let Some(parent_idx) = tree.get(node_idx).and_then(|n| n.parent) {
if let Some(parent_node) = tree.get(parent_idx) {
let pos = calculated_positions
.get(parent_idx)
.copied()
.unwrap_or_default();
let size = parent_node.used_size.unwrap_or_default();
let content_pos = LogicalPosition::new(
pos.x + parent_node.box_props.border.left + parent_node.box_props.padding.left,
pos.y + parent_node.box_props.border.top + parent_node.box_props.padding.top,
);
if let Some(dom_id) = parent_node.dom_node_id {
let styled_node_state = &styled_dom
.styled_nodes
.as_container()
.get(dom_id)
.map(|n| &n.styled_node_state)
.cloned()
.unwrap_or_default();
let writing_mode =
get_writing_mode(styled_dom, dom_id, styled_node_state).unwrap_or_default();
let content_size = parent_node.box_props.inner_size(size, writing_mode);
return (content_pos, content_size);
}
return (content_pos, size);
}
}
(viewport.origin, viewport.size)
}
#[derive(Debug)]
pub enum LayoutError {
InvalidTree,
SizingFailed,
PositioningFailed,
DisplayListFailed,
Text(crate::font_traits::LayoutError),
}
impl std::fmt::Display for LayoutError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LayoutError::InvalidTree => write!(f, "Invalid layout tree"),
LayoutError::SizingFailed => write!(f, "Sizing calculation failed"),
LayoutError::PositioningFailed => write!(f, "Position calculation failed"),
LayoutError::DisplayListFailed => write!(f, "Display list generation failed"),
LayoutError::Text(e) => write!(f, "Text layout error: {:?}", e),
}
}
}
impl From<crate::font_traits::LayoutError> for LayoutError {
fn from(err: crate::font_traits::LayoutError) -> Self {
LayoutError::Text(err)
}
}
impl std::error::Error for LayoutError {}
pub type Result<T> = std::result::Result<T, LayoutError>;