mod apply;
mod content;
mod counters;
mod inherit;
mod walk;
#[cfg(test)]
mod apply_tests;
#[cfg(test)]
mod tests;
use rdom_core::{Dom, NodeId};
use crate::ext::TuiExt;
use crate::style::{ComputedStyle, Content, Stylesheet};
pub trait CascadeExt {
fn cascade(&mut self, stylesheet: &Stylesheet);
fn cascade_all(&mut self, stylesheets: &[&Stylesheet]);
fn cascade_subtrees(&mut self, stylesheet: &Stylesheet, roots: &[NodeId]);
fn cascade_subtrees_all(&mut self, stylesheets: &[&Stylesheet], roots: &[NodeId]);
}
impl CascadeExt for Dom<TuiExt> {
fn cascade(&mut self, stylesheet: &Stylesheet) {
self.cascade_all(&[stylesheet]);
}
fn cascade_all(&mut self, stylesheets: &[&Stylesheet]) {
let merged_vars = walk::merge_root_vars(stylesheets);
let root = self.root();
let mut parent = ComputedStyle::initial();
parent.vars = merged_vars.clone();
let mut counters = walk::CounterState::default();
let _ = walk::cascade_subtree(self, stylesheets, root, &parent, &mut counters);
}
fn cascade_subtrees(&mut self, stylesheet: &Stylesheet, roots: &[NodeId]) {
self.cascade_subtrees_all(&[stylesheet], roots);
}
fn cascade_subtrees_all(&mut self, stylesheets: &[&Stylesheet], roots: &[NodeId]) {
let merged_vars = walk::merge_root_vars(stylesheets);
let uses_counters = stylesheets.iter().any(|s| {
s.rules().iter().any(|r| {
r.style.counter_reset.is_some()
|| r.style.counter_increment.is_some()
|| r.style
.content
.as_ref()
.and_then(|c| c.as_specified())
.is_some_and(Content::uses_counters)
})
});
let mut live: Vec<NodeId> = roots
.iter()
.copied()
.filter(|r| self.contains(*r))
.collect();
if live.is_empty() {
return;
}
if uses_counters {
let root = self.root();
live.retain(|r| {
*r == root
|| self
.compare_document_position(root, *r)
.contains(rdom_core::DocumentPosition::CONTAINED_BY)
});
live.sort_by(|a, b| tree_order(self, *a, *b));
live.dedup();
let mut next = 0usize;
let mut counters = walk::CounterState::default();
walk::cascade_roots_in_order(
self,
stylesheets,
&merged_vars,
&live,
&mut next,
root,
&mut counters,
);
return;
}
for root in live {
let parent_computed = walk::parent_computed_for(self, root, &merged_vars);
let mut counters = walk::CounterState::default();
let flags =
walk::cascade_subtree(self, stylesheets, root, &parent_computed, &mut counters);
walk::bubble_subtree_flags(self, root, flags);
}
}
}
fn tree_order(dom: &Dom<TuiExt>, a: NodeId, b: NodeId) -> std::cmp::Ordering {
use rdom_core::DocumentPosition;
use std::cmp::Ordering;
if a == b {
return Ordering::Equal;
}
let pos = dom.compare_document_position(a, b);
if pos.contains(DocumentPosition::FOLLOWING) {
Ordering::Less
} else {
Ordering::Greater
}
}
pub fn computed_of(dom: &Dom<TuiExt>, id: NodeId) -> ComputedStyle {
dom.node(id)
.ext()
.and_then(|e| e.computed.as_deref().cloned())
.unwrap_or_else(ComputedStyle::initial)
}