use std::collections::HashMap;
use crate::html::css_parse::{
CssRule, Position, PseudoClass, PseudoClassSet, selector_chain_matches_with_state,
};
use crate::layout::{LayoutBox, Rect};
use crate::tdom::node::{NodeArena, NodeId};
pub(super) fn incremental_region(
old_boxes: &[LayoutBox],
new_boxes: &[LayoutBox],
arena: &NodeArena,
dirty: &[NodeId],
) -> Option<Rect> {
fn index(boxes: &[LayoutBox], out: &mut HashMap<NodeId, Vec<Rect>>) {
for b in boxes {
out.entry(b.node_id).or_default().push(b.rect);
index(&b.children, out);
}
}
let mut old_index: HashMap<NodeId, Vec<Rect>> = HashMap::new();
let mut new_index: HashMap<NodeId, Vec<Rect>> = HashMap::new();
index(old_boxes, &mut old_index);
index(new_boxes, &mut new_index);
let mut region: Option<Rect> = None;
for &node_id in dirty {
let mut found = false;
let mut cur = Some(node_id);
while let Some(id) = cur {
if let Some(rects) = new_index.get(&id)
&& !rects.is_empty()
{
found = true;
let old_rects = old_index.get(&id);
for rect in rects {
match old_rects {
Some(old) if old.contains(rect) => {
region = Some(match region {
Some(r) => union_rect(r, *rect),
None => *rect,
});
}
_ => return None,
}
}
break;
}
cur = arena.get(id).and_then(|n| n.parent);
}
if !found {
return None;
}
}
region
}
pub(super) fn union_rect(a: Rect, b: Rect) -> Rect {
let left = a.x.min(b.x);
let top = a.y.min(b.y);
let right = a.x.saturating_add(a.width).max(b.x.saturating_add(b.width));
let bottom =
a.y.saturating_add(a.height)
.max(b.y.saturating_add(b.height));
Rect::new(left, top, right - left, bottom - top)
}
pub(super) fn find_box_rect(boxes: &[LayoutBox], node_id: NodeId) -> Option<Rect> {
fn walk(boxes: &[LayoutBox], node_id: NodeId) -> Option<Rect> {
for b in boxes {
if b.node_id == node_id {
return Some(b.rect);
}
if let Some(found) = walk(&b.children, node_id) {
return Some(found);
}
}
None
}
walk(boxes, node_id)
}
pub(super) fn pinned_boxes_rects(boxes: &[LayoutBox]) -> Vec<Rect> {
fn walk(boxes: &[LayoutBox], out: &mut Vec<Rect>) {
for b in boxes {
if matches!(b.positioning.position, Position::Fixed | Position::Sticky) {
out.push(b.rect);
}
walk(&b.children, out);
}
}
let mut out = Vec::new();
walk(boxes, &mut out);
out
}
pub(super) fn pseudo_affected_region(
boxes: &[LayoutBox],
arena: &NodeArena,
rules: &CssRule,
pseudo: PseudoClass,
target: Option<NodeId>,
) -> Option<Rect> {
let match_state = pseudo.state_with_target(target);
let mut region: Option<Rect> = None;
for id in pseudo.affected_candidates(arena, target) {
if arena.get(id).is_none() {
continue;
}
let affected = rules.0.iter().any(|entry| {
let sel = &entry.selector;
PseudoClassSet::from_selector(sel).contains_pseudo(pseudo)
&& selector_chain_matches_with_state(arena, id, sel, &match_state)
});
if affected {
let mut stack = vec![boxes];
while let Some(list) = stack.pop() {
for b in list {
if b.node_id == id {
region = Some(match region {
Some(r) => union_rect(r, b.rect),
None => b.rect,
});
}
stack.push(&b.children);
}
}
}
}
region
}