use crate::style::{ComputedStyle, DisplayInner};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeId(pub usize);
impl NodeId {
pub const ROOT: Self = NodeId(0);
}
#[derive(Debug, Clone)]
pub struct TextContent {
pub text: String,
}
#[derive(Debug, Clone)]
pub enum NodeKind {
Element,
Text(TextContent),
AnonymousBlock,
AnonymousInline,
}
#[derive(Debug, Clone)]
pub struct BoxTreeNode {
pub id: NodeId,
pub kind: NodeKind,
pub style: ComputedStyle,
pub children: Vec<NodeId>,
pub parent: Option<NodeId>,
}
impl BoxTreeNode {
pub fn new(id: NodeId, kind: NodeKind, style: ComputedStyle) -> Self {
Self {
id,
kind,
style,
children: Vec::new(),
parent: None,
}
}
pub fn is_text(&self) -> bool {
matches!(self.kind, NodeKind::Text(_))
}
pub fn text_content(&self) -> Option<&str> {
match &self.kind {
NodeKind::Text(tc) => Some(&tc.text),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct BoxTree {
nodes: Vec<BoxTreeNode>,
root: NodeId,
}
impl BoxTree {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
root: NodeId(0),
}
}
pub fn add_node(&mut self, kind: NodeKind, style: ComputedStyle) -> NodeId {
let id = NodeId(self.nodes.len());
self.nodes.push(BoxTreeNode::new(id, kind, style));
id
}
pub fn set_root(&mut self, id: NodeId) {
self.root = id;
}
pub fn append_child(&mut self, parent: NodeId, child: NodeId) {
self.nodes[child.0].parent = Some(parent);
self.nodes[parent.0].children.push(child);
}
pub fn node(&self, id: NodeId) -> &BoxTreeNode {
&self.nodes[id.0]
}
pub fn node_mut(&mut self, id: NodeId) -> &mut BoxTreeNode {
&mut self.nodes[id.0]
}
pub fn root(&self) -> NodeId {
self.root
}
pub fn children(&self, id: NodeId) -> &[NodeId] {
&self.nodes[id.0].children
}
pub fn style(&self, id: NodeId) -> &ComputedStyle {
&self.nodes[id.0].style
}
pub fn parent(&self, id: NodeId) -> Option<NodeId> {
self.nodes[id.0].parent
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn formatting_context(&self, id: NodeId) -> FormattingContextType {
let style = self.style(id);
match style.display.inner {
DisplayInner::Flex => FormattingContextType::Flex,
DisplayInner::Grid => FormattingContextType::Grid,
DisplayInner::Table => FormattingContextType::Table,
DisplayInner::Flow | DisplayInner::FlowRoot => {
let children = self.children(id);
if children.is_empty() {
return FormattingContextType::Block;
}
let has_block = children.iter().any(|&c| {
let cs = self.style(c);
cs.display.is_block_level() && !cs.is_out_of_flow()
});
if has_block {
FormattingContextType::Block
} else {
FormattingContextType::Inline
}
}
_ => FormattingContextType::Block,
}
}
}
impl Default for BoxTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormattingContextType {
Block,
Inline,
Flex,
Grid,
Table,
}
pub struct BoxTreeBuilder {
pub tree: BoxTree,
}
impl BoxTreeBuilder {
pub fn new() -> Self {
Self {
tree: BoxTree::new(),
}
}
pub fn root(&mut self, style: ComputedStyle) -> NodeId {
let id = self.tree.add_node(NodeKind::Element, style);
self.tree.set_root(id);
id
}
pub fn element(&mut self, parent: NodeId, style: ComputedStyle) -> NodeId {
let id = self.tree.add_node(NodeKind::Element, style);
self.tree.append_child(parent, id);
id
}
pub fn text(&mut self, parent: NodeId, text: &str) -> NodeId {
let id = self.tree.add_node(
NodeKind::Text(TextContent {
text: text.to_string(),
}),
ComputedStyle::inline(),
);
self.tree.append_child(parent, id);
id
}
pub fn build(self) -> BoxTree {
self.tree
}
}
impl Default for BoxTreeBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::ComputedStyle;
#[test]
fn test_build_simple_tree() {
let mut builder = BoxTreeBuilder::new();
let root = builder.root(ComputedStyle::block());
let child1 = builder.element(root, ComputedStyle::block());
let child2 = builder.element(root, ComputedStyle::block());
builder.text(child1, "Hello");
let tree = builder.build();
assert_eq!(tree.len(), 4);
assert_eq!(tree.children(root).len(), 2);
assert_eq!(tree.children(child1).len(), 1);
assert_eq!(tree.children(child2).len(), 0);
assert_eq!(tree.parent(child1), Some(root));
}
#[test]
fn test_formatting_context_detection() {
let mut builder = BoxTreeBuilder::new();
let root = builder.root(ComputedStyle::block());
builder.element(root, ComputedStyle::block());
builder.element(root, ComputedStyle::block());
let tree = builder.build();
assert_eq!(tree.formatting_context(root), FormattingContextType::Block);
}
}