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, HashMap}, sync::Arc};
use azul_core::{
dom::{DomId, NodeId},
geom::{LogicalPosition, LogicalRect, LogicalSize},
hit_test::{DocumentId, ScrollPosition},
resources::RendererResources,
selection::{TextCursor, TextSelection},
styled_dom::StyledDom,
};
pub const POSITION_UNSET: LogicalPosition = LogicalPosition { x: f32::MIN, y: f32::MIN };
const MAX_SCROLLBAR_REFLOW_ITERATIONS: usize = 10;
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 text_selections: &'a BTreeMap<DomId, TextSelection>,
pub debug_messages: &'a mut Option<Vec<LayoutDebugMessage>>,
pub counters: &'a mut HashMap<(usize, String), i32>,
pub viewport_size: LogicalSize,
pub fragmentation_context: Option<&'a mut crate::paged::FragmentationContext>,
pub cursor_is_visible: bool,
pub cursor_locations: Vec<(DomId, NodeId, TextCursor)>,
pub preedit_text: Option<String>,
pub dirty_text_overrides: BTreeMap<(DomId, NodeId), String>,
pub cache_map: cache::LayoutCacheMap,
pub image_cache: &'a azul_core::resources::ImageCache,
pub system_style: Option<std::sync::Arc<azul_css::system::SystemStyle>>,
pub get_system_time_fn: azul_core::task::GetSystemTimeCallback,
pub scrollbar_style_cache:
core::cell::RefCell<std::collections::HashMap<NodeId, crate::solver3::getters::ComputedScrollbarStyle>>,
}
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]
pub fn debug_info(&mut self, message: impl Into<String>) {
self.debug_info_inner(message.into());
}
#[inline]
pub fn debug_warning(&mut self, message: impl Into<String>) {
self.debug_warning_inner(message.into());
}
#[inline]
pub fn debug_error(&mut self, message: impl Into<String>) {
self.debug_error_inner(message.into());
}
#[inline]
pub fn debug_log(&mut self, message: &str) {
self.debug_log_inner(message.to_string());
}
#[inline]
pub fn debug_box_props(&mut self, message: impl Into<String>) {
self.debug_box_props_inner(message.into());
}
#[inline]
pub fn debug_css_getter(&mut self, message: impl Into<String>) {
self.debug_css_getter_inner(message.into());
}
#[inline]
pub fn debug_bfc_layout(&mut self, message: impl Into<String>) {
self.debug_bfc_layout_inner(message.into());
}
#[inline]
pub fn debug_ifc_layout(&mut self, message: impl Into<String>) {
self.debug_ifc_layout_inner(message.into());
}
#[inline]
pub fn debug_table_layout(&mut self, message: impl Into<String>) {
self.debug_table_layout_inner(message.into());
}
#[inline]
pub fn debug_display_type(&mut self, message: impl Into<String>) {
self.debug_display_type_inner(message.into());
}
}
#[cfg(feature = "text_layout")]
pub static SKIP_DISPLAY_LIST: core::sync::atomic::AtomicBool =
core::sync::atomic::AtomicBool::new(false);
pub fn set_skip_display_list(skip: bool) {
SKIP_DISPLAY_LIST.store(skip, core::sync::atomic::Ordering::Relaxed);
}
#[inline(never)]
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>,
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_locations: Vec<(DomId, NodeId, TextCursor)>,
preedit_text: Option<String>,
image_cache: &azul_core::resources::ImageCache,
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();
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0001u32); }
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 = HashMap::new();
let mut ctx_temp = LayoutContext {
scrollbar_style_cache: core::cell::RefCell::new(std::collections::HashMap::new()),
styled_dom: new_dom,
font_manager,
text_selections,
debug_messages,
counters: &mut counter_values,
viewport_size: viewport.size,
fragmentation_context: None,
cursor_is_visible,
cursor_locations: cursor_locations.clone(),
preedit_text: preedit_text.clone(),
dirty_text_overrides: BTreeMap::new(),
cache_map: cache::LayoutCacheMap::default(), image_cache,
system_style: system_style.clone(),
get_system_time_fn,
};
crate::probe::sample_peak_rss("rss:enter_layout_document");
let dom_ptr = new_dom as *const StyledDom as usize;
if dom_ptr == cache.prev_dom_ptr
&& viewport == cache.prev_viewport
&& cache.cached_display_list.is_some()
{
let _p = crate::probe::Probe::span("dom_ptr_cache_hit");
let (_, _, cached_dl) = cache.cached_display_list.as_ref().unwrap();
return Ok(cached_dl.clone());
}
cache.prev_dom_ptr = dom_ptr;
cache.prev_viewport = viewport;
crate::probe::reset_peak();
let (mut new_tree, mut recon_result) =
cache::reconcile_and_invalidate(&mut ctx_temp, cache, viewport)?;
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0002u32); }
crate::probe::sample_peak_rss("rss:after_reconcile");
crate::probe::sample_phase_peak("rss:peak_during_reconcile");
if let Some((cached_hash, cached_viewport, cached_dl)) = &cache.cached_display_list {
let new_root_hash = new_tree
.cold(new_tree.root)
.map(|c| c.subtree_hash);
if new_root_hash == Some(*cached_hash) && *cached_viewport == viewport {
let _p = crate::probe::Probe::span("display_list_cache_hit");
return Ok(cached_dl.clone());
}
}
for &node_idx in &recon_result.intrinsic_dirty {
if let Some(warm) = new_tree.warm_mut(node_idx) {
warm.taffy_cache.clear();
}
}
{
let _p = crate::probe::Probe::span("compute_counters");
cache::compute_counters(&new_dom, &new_tree, &mut counter_values);
}
let mut cache_map = std::mem::take(&mut cache.cache_map);
let _probe_cache_remap = Some(crate::probe::Probe::span("cache_map_remap"));
if let Some(old_tree) = cache.tree.as_ref() {
let mut remapped = cache::LayoutCacheMap::default();
remapped.entries.resize_with(new_tree.nodes.len(), Default::default);
for (dom_id, new_indices) in new_tree.dom_to_layout.iter() {
let Some(old_indices) = old_tree.dom_to_layout.get(dom_id) else {
continue;
};
for (pair_idx, &new_layout_idx) in new_indices.iter().enumerate() {
let Some(&old_layout_idx) = old_indices.get(pair_idx) else {
continue;
};
if old_layout_idx >= cache_map.entries.len()
|| new_layout_idx >= remapped.entries.len()
{
continue;
}
remapped.entries[new_layout_idx] =
core::mem::take(&mut cache_map.entries[old_layout_idx]);
}
}
fn collect_anon_children_by_parent(
tree: &LayoutTree,
) -> std::collections::HashMap<usize, Vec<usize>> {
let mut map: std::collections::HashMap<usize, Vec<usize>> =
std::collections::HashMap::new();
for (idx, node) in tree.nodes.iter().enumerate() {
if node.dom_node_id.is_some() {
continue;
}
if let Some(parent) = node.parent {
map.entry(parent).or_default().push(idx);
}
}
map
}
let old_anon_by_parent = collect_anon_children_by_parent(old_tree);
let new_anon_by_parent = collect_anon_children_by_parent(&new_tree);
let mut new_to_old_layout_idx: std::collections::HashMap<usize, usize> =
std::collections::HashMap::new();
for (dom_id, new_indices) in new_tree.dom_to_layout.iter() {
let Some(old_indices) = old_tree.dom_to_layout.get(dom_id) else {
continue;
};
for (pair_idx, &new_layout_idx) in new_indices.iter().enumerate() {
if let Some(&old_layout_idx) = old_indices.get(pair_idx) {
new_to_old_layout_idx.insert(new_layout_idx, old_layout_idx);
}
}
}
for (new_parent_idx, new_anon_children) in new_anon_by_parent {
let Some(&old_parent_idx) = new_to_old_layout_idx.get(&new_parent_idx) else {
continue;
};
let Some(old_anon_children) = old_anon_by_parent.get(&old_parent_idx) else {
continue;
};
for (ord, &new_anon_idx) in new_anon_children.iter().enumerate() {
let Some(&old_anon_idx) = old_anon_children.get(ord) else {
continue;
};
if old_anon_idx >= cache_map.entries.len()
|| new_anon_idx >= remapped.entries.len()
{
continue;
}
remapped.entries[new_anon_idx] =
core::mem::take(&mut cache_map.entries[old_anon_idx]);
}
}
cache_map = remapped;
} else {
cache_map.resize_to_tree(new_tree.nodes.len());
}
drop(_probe_cache_remap);
crate::probe::sample_peak_rss("rss:after_cache_remap");
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 {
scrollbar_style_cache: core::cell::RefCell::new(std::collections::HashMap::new()),
styled_dom: &new_dom,
font_manager,
text_selections,
debug_messages,
counters: &mut counter_values,
viewport_size: viewport.size,
fragmentation_context: None,
cursor_is_visible,
cursor_locations,
preedit_text,
dirty_text_overrides: BTreeMap::new(),
cache_map, image_cache,
system_style,
get_system_time_fn,
};
if recon_result.is_clean() && cache.tree.is_some() {
debug_log!(ctx, "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()
};
if SKIP_DISPLAY_LIST.load(core::sync::atomic::Ordering::Relaxed) {
return Ok(DisplayList::default());
}
return generate_display_list(
&mut ctx,
tree,
&cache.calculated_positions,
scroll_offsets,
&scroll_ids,
gpu_value_cache,
renderer_resources,
id_namespace,
dom_id,
);
}
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0003u32); }
let mut calculated_positions = cache.calculated_positions.clone();
let mut loop_count = 0;
loop {
loop_count += 1;
if loop_count > MAX_SCROLLBAR_REFLOW_ITERATIONS {
debug_warning!(ctx, "Scrollbar reflow loop hit limit of {} iterations, breaking to avoid infinite loop", MAX_SCROLLBAR_REFLOW_ITERATIONS);
break;
}
calculated_positions = {
let _p = crate::probe::Probe::span("clone_calculated_positions");
cache.calculated_positions.clone()
};
let mut reflow_needed_for_scrollbars = false;
{
crate::probe::reset_peak();
let _p = crate::probe::Probe::span("calc_intrinsic_sizes");
calculate_intrinsic_sizes(
&mut ctx,
&mut new_tree,
text_cache,
&recon_result.intrinsic_dirty,
)?;
}
crate::probe::sample_peak_rss("rss:after_calc_intrinsic");
crate::probe::sample_phase_peak("rss:peak_during_intrinsic");
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0005u32); }
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,
);
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0053u32); }
let root_node = &new_tree.nodes[root_idx];
let root_bp = root_node.box_props.unpack();
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0054u32); }
let is_root_with_margin = root_node.parent.is_none()
&& (root_bp.margin.left != 0.0 || root_bp.margin.top != 0.0);
let adjusted_cb_pos = if is_root_with_margin {
LogicalPosition::new(
cb_pos.x + root_bp.margin.left,
cb_pos.y + root_bp.margin.top,
)
} else {
cb_pos
};
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0056u32); }
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_bp.margin.left,
root_bp.margin.top
),
));
}
crate::probe::hint_purge_allocator();
crate::probe::sample_peak_rss("rss:before_root_layout");
crate::probe::reset_peak();
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0055u32); }
let _clr = {
let _p = crate::probe::Probe::span("root_layout_pass");
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,
)
};
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, if _clr.is_ok() { 0xDD00_0057u32 } else { 0xDD00_005Eu32 }); }
crate::probe::sample_peak_rss("rss:after_root_layout");
crate::probe::sample_phase_peak("rss:peak_during_root_layout");
if !pos_contains(&calculated_positions, root_idx) {
let root_node = &new_tree.nodes[root_idx];
let root_bp2 = root_node.box_props.unpack();
let root_position = LogicalPosition::new(
cb_pos.x + root_bp2.margin.left,
cb_pos.y + root_bp2.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_bp2.margin.top,
root_bp2.margin.right,
root_bp2.margin.bottom,
root_bp2.margin.left
),
));
}
pos_set(&mut calculated_positions, root_idx, root_position);
}
}
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0006u32); }
{
let _p = crate::probe::Probe::span("reposition_clean_subtrees");
cache::reposition_clean_subtrees(
&new_dom,
&new_tree,
&recon_result.layout_roots,
&mut calculated_positions,
);
}
if reflow_needed_for_scrollbars {
debug_log!(ctx,
"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;
}
{
let _p = crate::probe::Probe::span("adjust_relative_positions");
positioning::adjust_relative_positions(
&mut ctx,
&new_tree,
&mut calculated_positions,
viewport,
)?;
}
{
let _p = crate::probe::Probe::span("adjust_sticky_positions");
positioning::adjust_sticky_positions(
&mut ctx,
&new_tree,
&mut calculated_positions,
scroll_offsets,
viewport,
)?;
}
{
let _p = crate::probe::Probe::span("position_out_of_flow");
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) = {
let _p = crate::probe::Probe::span("compute_scroll_ids");
LayoutWindow::compute_scroll_ids(&new_tree, &new_dom)
};
crate::probe::sample_peak_rss("rss:before_display_list");
crate::probe::reset_peak();
let display_list = if SKIP_DISPLAY_LIST.load(core::sync::atomic::Ordering::Relaxed) {
DisplayList::default()
} else {
let _p = crate::probe::Probe::span("generate_display_list");
generate_display_list(
&mut ctx,
&new_tree,
&calculated_positions,
scroll_offsets,
&scroll_ids,
gpu_value_cache,
renderer_resources,
id_namespace,
dom_id,
)?
};
crate::probe::sample_phase_peak("rss:peak_during_display_list");
let _p_writeback = crate::probe::Probe::span("cache_writeback");
let cache_map_back = std::mem::take(&mut ctx.cache_map);
let root_subtree_hash = new_tree
.cold(new_tree.root)
.map(|c| c.subtree_hash)
.unwrap_or(crate::solver3::layout_tree::SubtreeHash(0));
cache.cached_display_list = Some((root_subtree_hash, viewport, display_list.clone()));
cache.tree = Some(new_tree);
cache.previous_positions = std::mem::replace(&mut 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;
unsafe { core::ptr::write_volatile(0x400A4 as *mut u32, 0xDD00_0004u32 | ((cache.calculated_positions.len() as u32 & 0xfff) << 4)); }
cache.counters = counter_values;
cache.cache_map = cache_map_back;
crate::probe::sample_peak_rss("rss:after_layout_document");
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 pbp = parent_node.box_props.unpack();
let content_pos = LogicalPosition::new(
pos.x + pbp.border.left + pbp.padding.left,
pos.y + pbp.border.top + pbp.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 = pbp.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>;