use rdom_core::{Dom, NodeId, NodeType};
use crate::ext::TuiExt;
use crate::layout::{Display, Overflow, Position, ZIndex};
use crate::node::TuiNodeExt;
use crate::render::Rect;
use crate::render::paint_pass::layout_rect_to_grid;
use crate::style::ComputedStyle;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct LayerEntry {
pub id: NodeId,
pub z: i16,
pub order: usize,
pub context: bool,
pub clip: Rect,
}
#[derive(Debug, Default)]
pub(crate) struct Layers {
pub negative: Vec<LayerEntry>,
pub zero_auto: Vec<LayerEntry>,
pub positive: Vec<LayerEntry>,
}
pub(crate) fn is_positioned(c: &ComputedStyle) -> bool {
c.position != Position::Static
}
pub(crate) fn creates_stacking_context(c: &ComputedStyle) -> bool {
(is_positioned(c) && !matches!(c.z_index, ZIndex::Auto)) || c.opacity < 1.0
}
pub(crate) fn children_clip(dom: &Dom<TuiExt>, id: NodeId, c: &ComputedStyle, clip: Rect) -> Rect {
let clips =
!matches!(c.overflow_x, Overflow::Visible) || !matches!(c.overflow_y, Overflow::Visible);
if !clips {
return clip;
}
let outer = dom.node(id).layout_rect().unwrap_or_default();
let padding_box = crate::layout::compute_padding_box(outer, c.border);
layout_rect_to_grid(padding_box, clip).unwrap_or_else(|| Rect::new(clip.x, clip.y, 0, 0))
}
#[derive(Clone, Copy)]
struct Frame {
positioned: bool,
content_clip: Rect,
}
pub(crate) fn collect_layers(
dom: &Dom<TuiExt>,
root: NodeId,
content_clip: Rect,
viewport: Rect,
) -> Layers {
let mut layers = Layers::default();
let root_positioned = dom
.node(root)
.ext()
.and_then(|e| e.computed.as_ref())
.is_some_and(|c| is_positioned(c));
let mut chain = vec![Frame {
positioned: root_positioned,
content_clip,
}];
let mut order = 0;
walk(dom, root, viewport, &mut chain, &mut layers, &mut order);
layers.negative.sort_by_key(|e| (e.z, e.order));
layers.positive.sort_by_key(|e| (e.z, e.order));
layers
}
fn walk(
dom: &Dom<TuiExt>,
id: NodeId,
viewport: Rect,
chain: &mut Vec<Frame>,
layers: &mut Layers,
order: &mut usize,
) {
for child in dom.node(id).child_nodes() {
let cid = child.id();
match child.node_type() {
NodeType::Fragment => {
walk(dom, cid, viewport, chain, layers, order);
continue;
}
NodeType::Element => {}
_ => continue,
}
let current = *chain
.last()
.expect("the context root frame is always present");
let Some(c) = child.ext().and_then(|e| e.computed.as_ref()) else {
chain.push(Frame {
positioned: false,
content_clip: current.content_clip,
});
walk(dom, cid, viewport, chain, layers, order);
chain.pop();
continue;
};
if c.display == Display::None {
continue;
}
if is_positioned(c) {
let clip = match c.position {
Position::Fixed => viewport,
Position::Absolute => chain
.iter()
.rev()
.find(|f| f.positioned)
.map_or(chain[0].content_clip, |f| f.content_clip),
Position::Relative | Position::Sticky | Position::Static => current.content_clip,
};
let context = creates_stacking_context(c);
let z = match c.z_index {
ZIndex::Auto => 0,
ZIndex::Value(n) => n,
};
let entry = LayerEntry {
id: cid,
z,
order: *order,
context,
clip,
};
*order += 1;
match z {
_ if !context => layers.zero_auto.push(entry),
z if z < 0 => layers.negative.push(entry),
0 => layers.zero_auto.push(entry),
_ => layers.positive.push(entry),
}
if !context {
chain.push(Frame {
positioned: true,
content_clip: children_clip(dom, cid, c, clip),
});
walk(dom, cid, viewport, chain, layers, order);
chain.pop();
}
} else if creates_stacking_context(c) {
} else {
chain.push(Frame {
positioned: false,
content_clip: children_clip(dom, cid, c, current.content_clip),
});
walk(dom, cid, viewport, chain, layers, order);
chain.pop();
}
}
}