use apace_traits::node_id::NodeId;
use std::cell::RefCell;
use apace_debug_timer::apace_debug_timer;
use kurbo::{Affine, Rect};
use parley::LayoutContext;
use selectors::Element as _;
use style::dom::TDocument;
#[cfg(feature = "parallel-construct")]
use rayon::prelude::*;
thread_local! {
pub(crate) static LAYOUT_CTX: RefCell<Option<Box<LayoutContext<TextBrush>>>> = const { RefCell::new(None) };
}
use takia::AvailableSpace;
use crate::{
BaseDocument,
layout::{
LayoutPassState,
construct::{
ConstructionTask, ConstructionTaskData, ConstructionTaskResult,
ConstructionTaskResultData, LayoutChildren, build_inline_layout_into,
collect_layout_children,
},
damage::{ALL_DAMAGE, CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC},
},
node::TextBrush,
profile::Phase,
};
impl BaseDocument {
pub fn resolve(&mut self, current_time_for_animations: f64) {
if TDocument::as_node(&self.root_node())
.first_element_child()
.is_none()
{
#[cfg(feature = "tracing")]
tracing::warn!("No DOM - not resolving");
return;
}
self.handle_messages();
if self.has_pending_critical_resources() {
return;
}
self.resolve_scroll_animation();
{
use crate::node::scrollbar::{FADE_DELAY, FADE_DURATION};
self.scrollbar_activity
.retain(|_, last| last.elapsed() < FADE_DELAY + FADE_DURATION);
}
let root_node_id = self.root_element().id;
apace_debug_timer!(timer, feature = "log-phase-times");
let style_span = self.profile_span(Phase::Style);
self.flush_pending_device_changes();
self.resolve_stylist(current_time_for_animations);
drop(style_span);
timer.record_time("style");
let damage_span = self.profile_span(Phase::Damage);
if !self.incremental_layout {
self.mark_all_damaged();
timer.record_time("mark_all");
}
self.propagate_damage_flags(root_node_id);
drop(damage_span);
timer.record_time("damage");
let construct_span = self.profile_span(Phase::Construct);
self.resolve_layout_children();
#[cfg(debug_assertions)]
self.assert_layout_parents_consistent();
drop(construct_span);
timer.record_time("construct");
let post_construct_span = self.profile_span(Phase::PostConstruct);
self.resolve_deferred_tasks();
self.flush_pending_style_images();
drop(post_construct_span);
timer.record_time("pconstruct");
let layout_span = self.profile_span(Phase::BoxLayout);
self.resolve_layout();
drop(layout_span);
timer.record_time("layout");
self.nodes.bump_geometry_generation();
let transform_span = self.profile_span(Phase::Transforms);
self.resolve_transforms(root_node_id);
for index in 0..self.top_layers.len() {
if !self.top_layer_is_rendered(self.top_layers[index].node_id) {
continue;
}
self.resolve_transforms(self.top_layers[index].node_id);
}
timer.record_time("transform");
if let Some(focus) = self.focus_node_id {
self.clamp_text_input_scroll(focus);
}
drop(transform_span);
let paint_tree_span = self.profile_span(Phase::PaintTree);
self.build_paint_tree(root_node_id);
for index in 0..self.top_layers.len() {
if !self.top_layer_is_rendered(self.top_layers[index].node_id) {
continue;
}
self.build_paint_tree(self.top_layers[index].node_id);
}
drop(paint_tree_span);
timer.record_time("paint_tree");
let doc_node_id = self.root_node().id;
let cleanup_span = self.profile_span(Phase::Cleanup);
self.clear_damage_and_dirty_flags(doc_node_id);
drop(cleanup_span);
timer.record_time("c_damage");
let hover_span = self.profile_span(Phase::Hover);
self.refresh_hover();
drop(hover_span);
let subdocuments_span = self.profile_span(Phase::Subdocuments);
let mut subdoc_is_animating = false;
for &node_id in &self.sub_document_nodes {
let node = &mut self.nodes[node_id];
let size = node.final_layout().size;
if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
let mut sub_viewport = sub_doc.viewport_mut();
sub_viewport.hidpi_scale = self.viewport.hidpi_scale;
sub_viewport.zoom = self.viewport.zoom;
sub_viewport.color_scheme = self.viewport.color_scheme;
let viewport_scale = self.viewport.scale();
sub_viewport.window_size = (
(size.width * viewport_scale) as u32,
(size.height * viewport_scale) as u32,
);
drop(sub_viewport);
sub_doc.resolve(current_time_for_animations);
subdoc_is_animating |= sub_doc.is_animating();
}
}
self.subdoc_is_animating = subdoc_is_animating;
drop(subdocuments_span);
timer.record_time("subdocs");
timer.print_times(&format!("Resolve({}): ", self.id()));
}
fn resolve_transforms(&mut self, node_id: NodeId) -> Rect {
if !self.nodes.contains_key(node_id) {
return Rect::ZERO;
}
let scale = self.viewport.scale_f64();
if !self.nodes[node_id]
.damage()
.map(|d| d.contains(style::selector_parser::RestyleDamage::RECALCULATE_OVERFLOW))
.unwrap_or(false)
{
let node = &self.nodes[node_id];
let location = node.final_layout().location.map(|v| v as f64 * scale);
let mut transform = Affine::translate((location.x, location.y));
if let Some(t) = node.transform().as_deref() {
transform *= *t
}
let overflow = *node.scrollable_overflow();
return transform.transform_rect_bbox(overflow);
}
let transform = self.nodes[node_id].set_transform(scale as f32);
let w = self.nodes[node_id].final_layout().size.width as f64 * scale;
let h = self.nodes[node_id].final_layout().size.height as f64 * scale;
let mut overflow = Rect::new(0.0, 0.0, w, h);
let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
if let Some(ref children) = layout_children {
for &child_id in children {
if self.nodes[child_id].is_out_of_flow() {
continue;
}
let child_rect_in_self = self.resolve_transforms(child_id);
overflow = overflow.union(child_rect_in_self);
}
}
let hoisted_children =
std::mem::take(&mut *self.nodes[node_id].hoisted_children.borrow_mut());
for &child_id in &hoisted_children {
if !self.nodes.contains_key(child_id) {
continue;
}
let child_rect_in_self = self.resolve_transforms(child_id);
overflow = overflow.union(child_rect_in_self);
}
*self.nodes[node_id].hoisted_children.borrow_mut() = hoisted_children;
for pseudo in [self.nodes[node_id].before(), self.nodes[node_id].after()] {
let Some(pseudo) = pseudo else { continue };
if self.nodes[pseudo].is_out_of_flow() {
continue;
}
let child_rect_in_self = self.resolve_transforms(pseudo);
overflow = overflow.union(child_rect_in_self);
}
*self.nodes[node_id].scrollable_overflow_mut() = overflow;
*self.nodes[node_id].layout_children.get_mut() = layout_children;
let scaled_x = self.nodes[node_id].final_layout().location.x as f64 * scale;
let scaled_y = self.nodes[node_id].final_layout().location.y as f64 * scale;
let full = if let Some(t) = transform {
Affine::translate((scaled_x, scaled_y)) * t
} else {
Affine::translate((scaled_x, scaled_y))
};
full.transform_rect_bbox(overflow)
}
pub fn resolve_layout_children(&mut self) {
resolve_layout_children_recursive(self, self.root_node().id);
for index in 0..self.top_layers.len() {
let id = self.top_layers[index].node_id;
if !self.top_layer_is_rendered(id) {
continue;
}
resolve_layout_children_recursive(self, id);
self.nodes[id].layout_parent.set(None);
self.nodes[id].oof_containing_block.set(None);
}
fn resolve_layout_children_recursive(doc: &mut BaseDocument, node_id: NodeId) {
if doc.nodes.get(node_id).is_none() {
return;
}
let mut damage = doc.nodes[node_id].damage().unwrap_or(ALL_DAMAGE);
let _flags = doc.nodes[node_id].flags;
if damage.intersects(CONSTRUCT_FC | CONSTRUCT_BOX) {
let old_anonymous_blocks = std::mem::take(&mut doc.nodes[node_id].anonymous_blocks);
for anon_id in old_anonymous_blocks {
doc.deallocate_anonymous_block(anon_id);
}
let mut collected = LayoutChildren::default();
collect_layout_children(doc, node_id, &mut collected);
let mut layout_children = collected.children;
layout_children.retain(|id| !doc.nodes[*id].flags.is_top_layer());
doc.nodes[node_id].anonymous_blocks = collected.anonymous_blocks;
for child_id in layout_children.iter().copied() {
resolve_layout_children_recursive(doc, child_id);
doc.nodes[child_id].layout_parent.set(Some(node_id));
if let Some(mut data) = doc.nodes[child_id]
.try_stylo_element_data_mut()
.and_then(|s| s.get_mut())
{
data.damage
.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
}
}
*doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children.clone());
damage.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
} else {
let layout_children = doc.nodes[node_id].layout_children.borrow_mut().take();
if let Some(layout_children) = layout_children {
for child_id in layout_children.iter().copied() {
if !doc.nodes.contains_key(child_id) {
continue;
}
resolve_layout_children_recursive(doc, child_id);
doc.nodes[child_id].layout_parent.set(Some(node_id));
}
*doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children);
}
}
doc.nodes[node_id].set_damage(damage);
}
}
#[cfg(debug_assertions)]
fn assert_layout_parents_consistent(&self) {
use crate::node::NodeFlags;
for (parent_id, node) in self.nodes.iter() {
if !node.flags.contains(NodeFlags::IS_IN_DOCUMENT) {
continue;
}
let Some(children) = node.layout_children.borrow().clone() else {
continue;
};
for child_id in children {
let Some(child) = self.nodes.get(child_id) else {
panic!("layout child {child_id:?} of {parent_id:?} is not in the slab");
};
debug_assert_eq!(
child.layout_parent.get(),
Some(parent_id),
"layout_parent of {child_id:?} does not point at its layout container {parent_id:?}"
);
}
}
}
pub fn resolve_deferred_tasks(&mut self) {
let mut deferred_construction_nodes = std::mem::take(&mut self.deferred_construction_nodes);
deferred_construction_nodes.sort_unstable_by_key(|task| task.node_id);
deferred_construction_nodes.dedup_by_key(|task| task.node_id);
#[cfg(feature = "parallel-construct")]
let iter = deferred_construction_nodes.into_par_iter();
#[cfg(not(feature = "parallel-construct"))]
let iter = deferred_construction_nodes.into_iter();
let results: Vec<ConstructionTaskResult> = iter
.map(|task: ConstructionTask| match task.data {
ConstructionTaskData::InlineLayout(mut layout) => {
#[cfg(feature = "parallel-construct")]
let mut layout_ctx = LAYOUT_CTX
.take()
.unwrap_or_else(|| Box::new(LayoutContext::new()));
#[cfg(feature = "parallel-construct")]
let layout_ctx_mut = &mut layout_ctx;
#[cfg(feature = "parallel-construct")]
let mut font_ctx = self
.thread_font_contexts
.get_or(|| RefCell::new(Box::new(self.font_ctx.lock().unwrap().clone())))
.borrow_mut();
#[cfg(feature = "parallel-construct")]
let font_ctx_mut = &mut *font_ctx;
#[cfg(not(feature = "parallel-construct"))]
let layout_ctx_mut = &mut self.layout_ctx;
#[cfg(not(feature = "parallel-construct"))]
let font_ctx_mut = &mut *self.font_ctx.lock().unwrap();
layout.content_widths = None;
build_inline_layout_into(
&self.nodes,
layout_ctx_mut,
font_ctx_mut,
&mut layout,
self.viewport.scale(),
task.node_id,
self.profile.as_ref(),
);
#[cfg(feature = "parallel-construct")]
{
LAYOUT_CTX.set(Some(layout_ctx));
}
ConstructionTaskResult {
node_id: task.node_id,
data: ConstructionTaskResultData::InlineLayout(layout),
}
}
})
.collect();
for result in results {
match result.data {
ConstructionTaskResultData::InlineLayout(layout) => {
self.nodes[result.node_id].clear_layout_cache();
self.nodes[result.node_id]
.element_data_mut()
.unwrap()
.inline_layout_data = Some(layout);
}
}
}
self.deferred_construction_nodes.clear();
}
pub fn resolve_layout(&mut self) {
let size = self.stylist.device().au_viewport_size();
let available_space = takia::Size {
width: AvailableSpace::Definite(size.width.to_f32_px()),
height: AvailableSpace::Definite(size.height.to_f32_px()),
};
let root_element_id = crate::takia_node_id(self.root_element().id);
let layers: Vec<_> = self
.top_layers
.iter()
.map(|layer| layer.node_id)
.filter(|id| self.top_layer_is_rendered(*id))
.collect();
let mut state = LayoutPassState::new(self);
#[cfg(feature = "writing-mode")]
let available_space = {
state.layout_wm = state.layout_wm_of(crate::dom_node_id(root_element_id));
if state.layout_wm.is_vertical() {
available_space.transpose()
} else {
available_space
}
};
takia::compute_root_layout(&mut state, root_element_id, available_space);
#[cfg(feature = "writing-mode")]
state.physicalise_and_round_layout(root_element_id);
#[cfg(not(feature = "writing-mode"))]
takia::round_layout(&mut state, root_element_id);
for id in layers {
let id = crate::takia_node_id(id);
#[cfg(feature = "writing-mode")]
{
state.layout_wm = state.layout_wm_of(crate::dom_node_id(id));
}
#[cfg(feature = "writing-mode")]
let layer_space = if state.layout_wm.is_vertical() {
takia::Size {
width: AvailableSpace::Definite(size.height.to_f32_px()),
height: AvailableSpace::Definite(size.width.to_f32_px()),
}
} else {
takia::Size {
width: AvailableSpace::Definite(size.width.to_f32_px()),
height: AvailableSpace::Definite(size.height.to_f32_px()),
}
};
#[cfg(not(feature = "writing-mode"))]
let layer_space = available_space;
takia::compute_root_layout(&mut state, id, layer_space);
#[cfg(feature = "writing-mode")]
state.physicalise_and_round_layout(id);
#[cfg(not(feature = "writing-mode"))]
takia::round_layout(&mut state, id);
}
}
}