use rdom_core::{Dom, NodeId, NodeType};
use crate::ext::{StyleSlot, TuiExt};
use crate::layout::{Display, Flow, Position, WhiteSpace};
use crate::node::TuiNodeExt;
pub(crate) fn static_pseudo_text(dom: &Dom<TuiExt>, host: NodeId, slot: StyleSlot) -> Option<&str> {
let node = dom.node(host);
let computed = match slot {
StyleSlot::Before => node.computed_before(),
StyleSlot::After => node.computed_after(),
StyleSlot::Host => None,
}?;
if computed.position != Position::Static {
return None;
}
computed.content.as_deref()
}
pub(crate) fn own_inline_pseudo_text(
dom: &Dom<TuiExt>,
host: NodeId,
slot: StyleSlot,
) -> Option<&str> {
if slot == StyleSlot::Before && marker_line_holder(dom, host).is_some() {
return None;
}
static_pseudo_text(dom, host, slot)
}
pub(crate) fn own_line_pseudos(dom: &Dom<TuiExt>, host: NodeId) -> super::RunPseudos {
if !is_block_flow_container(dom, host) {
return super::RunPseudos::default();
}
let visible =
|slot| own_inline_pseudo_text(dom, host, slot).is_some_and(|t| !t.trim().is_empty());
let block_edge =
|from_end| line_bearing_child(dom, host, from_end).is_some_and(|c| is_block_level(dom, c));
super::RunPseudos {
before: visible(StyleSlot::Before) && block_edge(false),
after: visible(StyleSlot::After) && block_edge(true),
}
}
pub(crate) fn marker_line_holder(dom: &Dom<TuiExt>, host: NodeId) -> Option<NodeId> {
if dom.node(host).tag_name() != Some("li")
|| static_pseudo_text(dom, host, StyleSlot::Before).is_none()
|| !is_block_flow_container(dom, host)
{
return None;
}
let first = line_bearing_child(dom, host, false)?;
if !is_block_level(dom, first) {
return None;
}
first_line_holder(dom, first)
}
pub(crate) fn deferred_markers(dom: &Dom<TuiExt>, holder: NodeId) -> Vec<NodeId> {
let mut markers = Vec::new();
let mut cur = holder;
while let Some(parent) = dom.node(cur).parent_node().map(|p| p.id()) {
if !is_block_flow_container(dom, parent)
|| line_bearing_child(dom, parent, false) != Some(cur)
{
break;
}
if marker_line_holder(dom, parent) == Some(holder) {
markers.push(parent);
}
cur = parent;
}
markers.reverse();
markers
}
pub(crate) fn inline_content_at_edge(dom: &Dom<TuiExt>, host: NodeId, from_end: bool) -> bool {
line_bearing_child(dom, host, from_end).is_some_and(|c| !is_block_level(dom, c))
}
pub(crate) fn run_pseudos(
dom: &Dom<TuiExt>,
host: NodeId,
direct_children: &[NodeId],
) -> super::RunPseudos {
let holds_edge = |from_end| {
line_bearing_child(dom, host, from_end).is_none_or(|c| direct_children.contains(&c))
};
super::RunPseudos {
before: holds_edge(false),
after: holds_edge(true),
}
}
fn first_line_holder(dom: &Dom<TuiExt>, el: NodeId) -> Option<NodeId> {
if !is_block_flow_container(dom, el) {
return None;
}
let first = line_bearing_child(dom, el, false)?;
if is_block_level(dom, first) {
first_line_holder(dom, first)
} else {
Some(el)
}
}
fn line_bearing_child(dom: &Dom<TuiExt>, host: NodeId, from_end: bool) -> Option<NodeId> {
let collapses = dom
.node(host)
.computed()
.is_none_or(|c| matches!(c.white_space, WhiteSpace::Normal | WhiteSpace::NoWrap));
let bears_line = |c: NodeId| {
let node = dom.node(c);
match node.node_type() {
NodeType::Text => {
!(collapses
&& node
.node_value()
.is_none_or(|t| t.chars().all(char::is_whitespace)))
}
NodeType::Element => crate::render::layout_pass::is_in_flow(dom, c),
_ => false,
}
};
let mut children = dom.node(host).child_nodes().map(|c| c.id());
if from_end {
children.filter(|&c| bears_line(c)).last()
} else {
children.find(|&c| bears_line(c))
}
}
fn is_block_flow_container(dom: &Dom<TuiExt>, id: NodeId) -> bool {
dom.node(id)
.computed()
.is_some_and(|c| c.display == Display::Block && c.flow == Flow::Block)
}
fn is_block_level(dom: &Dom<TuiExt>, id: NodeId) -> bool {
let node = dom.node(id);
node.node_type() == NodeType::Element
&& node.computed().is_none_or(|c| c.display == Display::Block)
}