use rdom_core::{Dom, NodeId, NodeType};
use crate::ext::TuiExt;
use crate::layout::{Display, LayoutRect};
use crate::render::{Buffer, Rect};
pub(super) fn paint_group(
dom: &Dom<TuiExt>,
root: NodeId,
buf: &mut Buffer,
alpha: f32,
paint: impl Fn(&mut Buffer),
) {
let region = layer_region(dom, root, buf.area);
let mut layer = buf.copy_region(region);
paint(&mut layer);
#[cfg(test)]
let unbounded = {
let mut full = buf.clone();
let mut full_layer = buf.clone();
paint(&mut full_layer);
full.composite_group(&full_layer, alpha);
full
};
buf.composite_group(&layer, alpha);
#[cfg(test)]
{
assert_eq!(*buf, unbounded, "bounded layer diverged for {root:?}");
assert_eq!(buf.border_dirs, unbounded.border_dirs);
assert_eq!(buf.half_block_quads, unbounded.half_block_quads);
}
}
pub(super) fn layer_region(dom: &Dom<TuiExt>, root: NodeId, area: Rect) -> Rect {
let mut rows: Option<(i64, i64)> = None;
collect_rows(dom, root, &mut rows);
let Some((top, bottom)) = rows else {
return Rect::new(area.x, area.y, area.width, 0);
};
let top = (top - 1).max(i64::from(area.y));
let bottom = (bottom + 1).min(i64::from(area.bottom()));
if bottom <= top {
return Rect::new(area.x, area.y, area.width, 0);
}
Rect::new(area.x, top as u16, area.width, (bottom - top) as u16)
}
fn extend(rows: &mut Option<(i64, i64)>, top: i64, bottom: i64) {
if bottom <= top {
return;
}
*rows = Some(match *rows {
None => (top, bottom),
Some((t, b)) => (t.min(top), b.max(bottom)),
});
}
fn extend_rect(rows: &mut Option<(i64, i64)>, r: LayoutRect) {
extend(rows, i64::from(r.y), i64::from(r.y) + i64::from(r.height));
}
fn collect_rows(dom: &Dom<TuiExt>, id: NodeId, rows: &mut Option<(i64, i64)>) {
let node = dom.node(id);
if node.node_type() == NodeType::Element
&& let Some(ext) = node.ext()
{
if ext
.computed
.as_ref()
.is_some_and(|c| c.display == Display::None)
{
return;
}
extend_rect(rows, ext.layout);
for anon in &ext.anonymous_blocks {
extend_rect(rows, anon.rect);
}
for pseudo in [ext.before_layout, ext.after_layout].into_iter().flatten() {
extend_rect(rows, pseudo.rect);
}
if let Some(layout) = &ext.inline_layout {
let y = i64::from(ext.content_layout.y);
extend(rows, y, y + layout.lines.len() as i64);
}
}
for child in node.child_nodes() {
if matches!(child.node_type(), NodeType::Element | NodeType::Fragment) {
collect_rows(dom, child.id(), rows);
}
}
}