use blitz_traits::node_id::NodeId;
use std::cell::Cell;
use std::ops::Range;
use thin_vec::ThinVec;
use crate::layout::damage::ALL_DAMAGE;
use crate::tree::NodeTree;
use crate::{BaseDocument, Node};
use style::properties::generated::longhands::position::computed_value::T as Position;
use style::selector_parser::RestyleDamage;
use style::values::computed::Float;
use style::values::specified::box_::DisplayInside;
use taffy::{Point, Rect};
pub fn hoisted_child_position(tree: &NodeTree, sc_root_id: NodeId, child_id: NodeId) -> Point<f32> {
let child = &tree[child_id];
let mut position = Point::ZERO;
let mut current = child.containing_block();
if child.taffy_position() == taffy::Position::Fixed
&& let Some(cb) = current.and_then(|id| tree.get(id))
&& cb.containing_block().is_none()
{
let scroll_offset = *cb.scroll_offset();
let viewport_scroll = tree.viewport_scroll();
position.x += scroll_offset.x as f32 + viewport_scroll.x as f32;
position.y += scroll_offset.y as f32 + viewport_scroll.y as f32;
}
while let Some(id) = current {
if id == sc_root_id {
break;
}
let Some(node) = tree.get(id) else {
break;
};
let location = node.final_layout().location;
let scroll_offset = *node.scroll_offset();
position.x += location.x - scroll_offset.x as f32;
position.y += location.y - scroll_offset.y as f32;
current = node.containing_block();
}
position
}
#[derive(Debug, Clone)]
pub struct HoistedPaintChild {
pub node_id: NodeId,
pub z_index: i32,
position: Cell<(u64, Point<f32>)>,
}
impl HoistedPaintChild {
fn new(node_id: NodeId, z_index: i32) -> Self {
Self {
node_id,
z_index,
position: Cell::new((0, Point::ZERO)),
}
}
pub fn position(&self, tree: &NodeTree, sc_root_id: NodeId) -> Point<f32> {
let generation = tree.geometry_generation();
let (stamp, cached) = self.position.get();
if stamp == generation {
return cached;
}
let position = hoisted_child_position(tree, sc_root_id, self.node_id);
self.position.set((generation, position));
position
}
pub fn position_is_cached(&self, tree: &NodeTree) -> bool {
self.position.get().0 == tree.geometry_generation()
}
}
#[derive(Debug)]
pub struct StackingContext {
pub children: ThinVec<HoistedPaintChild>,
pub negative_z_count: u32,
hoisted_content_bbox: Cell<(u64, Rect<f32>)>,
}
impl StackingContext {
fn from_children(children: ThinVec<HoistedPaintChild>) -> Self {
let mut sc = Self {
children,
negative_z_count: 0,
hoisted_content_bbox: Cell::new((0, Rect::ZERO)),
};
sc.sort();
sc
}
pub fn hoisted_content_bbox(
&self,
tree: &NodeTree,
sc_root_id: NodeId,
scale: f64,
) -> Rect<f32> {
let generation = tree.geometry_generation();
let (stamp, cached) = self.hoisted_content_bbox.get();
if stamp == generation {
return cached;
}
let child_rect = |child: &HoistedPaintChild| -> Rect<f32> {
let node = &tree[child.node_id];
let position = child.position(tree, sc_root_id);
let location = node.final_layout().location;
let mut overflow = *node.scrollable_overflow();
if let Some(transform) = node.transform().as_deref() {
overflow = transform.transform_rect_bbox(overflow);
}
let left = position.x + location.x;
let top = position.y + location.y;
Rect {
left: left + (overflow.x0 / scale) as f32,
top: top + (overflow.y0 / scale) as f32,
right: left + (overflow.x1 / scale) as f32,
bottom: top + (overflow.y1 / scale) as f32,
}
};
let mut area = Rect::ZERO;
if let Some((first, rest)) = self.children.split_first() {
area = child_rect(first);
for child in rest {
let pos = child_rect(child);
area.left = area.left.min(pos.left);
area.top = area.top.min(pos.top);
area.right = area.right.max(pos.right);
area.bottom = area.bottom.max(pos.bottom);
}
}
self.hoisted_content_bbox.set((generation, area));
area
}
pub fn sort(&mut self) {
self.children.sort_by_key(|c| c.z_index);
self.negative_z_count = self.children.iter().take_while(|c| c.z_index < 0).count() as u32;
}
pub fn neg_z_range(&self) -> Range<usize> {
0..(self.negative_z_count as usize)
}
pub fn pos_z_range(&self) -> Range<usize> {
(self.negative_z_count as usize)..self.children.len()
}
pub fn neg_z_hoisted_children(
&self,
) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
self.children[self.neg_z_range()].iter()
}
pub fn pos_z_hoisted_children(
&self,
) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
self.children[self.pos_z_range()].iter()
}
}
impl BaseDocument {
pub(crate) fn build_paint_tree(&mut self, root_id: NodeId) {
self.build_paint_tree_impl(root_id, None);
}
fn build_paint_tree_impl(
&mut self,
node_id: NodeId,
stacking_context: Option<&mut ThinVec<HoistedPaintChild>>,
) {
{
let node = &self.nodes[node_id];
let role_unchanged = node.stacking_context.is_some() == stacking_context.is_none();
let clean = role_unchanged
&& !node
.damage()
.unwrap_or(ALL_DAMAGE)
.contains(RestyleDamage::REBUILD_STACKING_CONTEXT)
&& !node.is_anonymous();
if clean {
if let Some(stacking_context) = stacking_context {
stacking_context.extend(node.sc_contribution_cache.borrow().iter().cloned());
}
return;
}
}
let Some(display) = self.nodes[node_id].display_style() else {
return;
};
let mut entries = {
let node = &mut self.nodes[node_id];
let mut entries = match stacking_context {
None => node
.stacking_context
.take()
.map(|sc| sc.children)
.unwrap_or_default(),
Some(_) => std::mem::take(&mut *node.sc_contribution_cache.borrow_mut()),
};
entries.clear();
entries
};
let children = self.nodes[node_id].layout_children.borrow_mut().take();
if let Some(children) = children {
let is_flex_or_grid =
matches!(display.inside(), DisplayInside::Flex | DisplayInside::Grid);
let owned_here = |doc: &Self, child_id: NodeId| -> bool {
doc.nodes[child_id]
.oof_containing_block
.get()
.is_none_or(|cb| cb == node_id)
};
let mut hoisted_past_parent: Vec<(usize, NodeId)> = self.nodes[node_id]
.hoisted_children
.borrow()
.iter()
.copied()
.filter(|&id| {
self.nodes
.get(id)
.is_some_and(|n| n.layout_parent.get() != Some(node_id))
})
.map(|id| {
let anchor = self
.layout_child_containing(node_id, id)
.and_then(|anchor| children.iter().position(|&c| c == anchor))
.unwrap_or(children.len());
(anchor, id)
})
.collect();
hoisted_past_parent.sort_by_key(|&(anchor, _)| anchor);
let mut next_hoisted = 0;
let mut paint_children = self.nodes[node_id]
.paint_children
.borrow_mut()
.take()
.unwrap_or_default();
paint_children.clear();
paint_children.reserve(children.len() + hoisted_past_parent.len());
for (index, &child_id) in children.iter().enumerate() {
if owned_here(self, child_id) {
let is_sc_root = self.nodes[child_id].is_stacking_context_root(is_flex_or_grid);
self.build_paint_tree_impl(
child_id,
match is_sc_root {
true => None,
false => Some(&mut entries),
},
);
let z_index = self.nodes[child_id].z_index();
if is_sc_root && z_index != 0 {
entries.push(HoistedPaintChild::new(child_id, z_index))
} else {
paint_children.push(child_id);
}
}
while let Some(&(anchor, hoisted_id)) = hoisted_past_parent.get(next_hoisted)
&& anchor == index
{
self.attach_hoisted_past_parent(hoisted_id, &mut paint_children, &mut entries);
next_hoisted += 1;
}
}
for &(_, hoisted_id) in &hoisted_past_parent[next_hoisted..] {
self.attach_hoisted_past_parent(hoisted_id, &mut paint_children, &mut entries);
}
paint_children
.sort_by_cached_key(|id| node_to_paint_order(&self.nodes[*id], is_flex_or_grid));
let node = &self.nodes[node_id];
*node.paint_children.borrow_mut() = Some(paint_children);
*node.layout_children.borrow_mut() = Some(children);
}
let node = &mut self.nodes[node_id];
if let Some(stacking_context) = stacking_context {
node.stacking_context = None;
stacking_context.extend(entries.iter().cloned());
*node.sc_contribution_cache.borrow_mut() = entries;
} else {
node.sc_contribution_cache.borrow_mut().clear();
node.stacking_context = Some(Box::new(StackingContext::from_children(entries)));
}
}
fn layout_child_containing(&self, node_id: NodeId, id: NodeId) -> Option<NodeId> {
let mut current = id;
loop {
let parent = self.nodes.get(current)?.layout_parent.get()?;
if parent == node_id {
return Some(current);
}
current = parent;
}
}
fn attach_hoisted_past_parent(
&mut self,
child_id: NodeId,
paint_children: &mut ThinVec<NodeId>,
entries: &mut ThinVec<HoistedPaintChild>,
) {
self.build_paint_tree_impl(
child_id,
match self.nodes[child_id].is_stacking_context_root(false) {
true => None,
false => Some(entries),
},
);
let z_index = self.nodes[child_id].z_index();
if z_index != 0 {
entries.push(HoistedPaintChild::new(child_id, z_index));
} else {
paint_children.push(child_id);
}
}
}
#[inline(always)]
fn position_to_order(pos: Position) -> i32 {
match pos {
Position::Static => 0,
Position::Relative | Position::Sticky | Position::Absolute | Position::Fixed => 2,
}
}
#[inline(always)]
fn float_to_order(pos: Float) -> i32 {
match pos {
Float::None => 0,
_ => 1,
}
}
#[inline(always)]
fn node_to_paint_order(node: &Node, is_flex_or_grid: bool) -> (i32, i32) {
let Some(style) = node.primary_styles() else {
return (0, 0);
};
let position = style.clone_position();
if is_flex_or_grid {
match position {
Position::Static => (0, style.clone_order()),
Position::Relative | Position::Sticky => (2, style.clone_order()),
Position::Absolute | Position::Fixed => (2, 0),
}
} else {
(
position_to_order(position) + float_to_order(style.clone_float()),
0,
)
}
}