///|
/// DOM Bridge - Connects DomTree mutations to LayoutTree updates
///
/// This module provides the integration layer between the DOM and Layout systems,
/// enabling automatic layout updates when the DOM changes.
// =============================================================================
// Document: Unified DOM + Layout Container
// =============================================================================
///|
/// Document combines DomTree and LayoutTree with automatic synchronization
pub struct Document {
/// The DOM tree (source of truth for structure)
dom : @dom.DomTree
/// The layout tree (computed layout)
layout : LayoutTree
/// Mapping from DOM NodeId to LayoutTree uid
dom_to_layout : Map[Int, Int]
/// Mapping from LayoutTree uid to DOM NodeId
layout_to_dom : Map[Int, Int]
/// Whether layout needs recomputation
mut needs_layout : Bool
}
///|
/// Create a new Document with the given viewport dimensions
pub fn Document::new(
viewport_width : Double,
viewport_height : Double,
) -> Document {
let dom = @dom.DomTree::new()
// Create a minimal root LayoutNode
let root_layout = LayoutNode::create_with_id("root")
root_layout.style.width = @types.Percent(1.0)
root_layout.style.height = @types.Percent(1.0)
let layout = LayoutTree::new(root_layout, viewport_width, viewport_height)
{ dom, layout, dom_to_layout: {}, layout_to_dom: {}, needs_layout: false }
}
///|
/// Get the DomTree
pub fn Document::get_dom(self : Document) -> @dom.DomTree {
self.dom
}
///|
/// Get the LayoutTree
pub fn Document::get_layout(self : Document) -> LayoutTree {
self.layout
}
///|
/// Check if layout needs recomputation
pub fn Document::needs_layout(self : Document) -> Bool {
self.needs_layout || self.layout.needs_layout()
}
///|
/// Process pending DOM mutations and apply them to the layout tree
/// Returns the number of mutations processed
pub fn Document::process_mutations(self : Document) -> Int {
let (records, result) = self.dom.flush_mutations()
if records.is_empty() {
return 0
}
for record in records {
self.apply_mutation(record)
}
if result.needs_layout {
self.needs_layout = true
}
result.processed
}
///|
/// Compute layout if needed
pub fn Document::compute_layout(self : Document) -> @types.Layout {
self.needs_layout = false
if self.layout.needs_layout() {
self.layout.compute_incremental()
} else {
self.layout.compute_full()
}
}
///|
/// Force full layout recomputation
pub fn Document::compute_layout_full(self : Document) -> @types.Layout {
self.needs_layout = false
self.layout.compute_full()
}
///|
/// Resize viewport and trigger layout
pub fn Document::resize_viewport(
self : Document,
width : Double,
height : Double,
) -> Unit {
self.layout.resize_viewport(width, height)
self.needs_layout = true
}
// =============================================================================
// Mutation Application
// =============================================================================
///|
/// Apply a single mutation record to the layout tree
fn Document::apply_mutation(
self : Document,
record : @dom.MutationRecord,
) -> Unit {
match record.type_ {
@dom.ChildList => self.apply_child_list_mutation(record)
@dom.Attributes => self.apply_attributes_mutation(record)
@dom.CharacterData => self.apply_character_data_mutation(record)
@dom.StyleChange => self.apply_style_change_mutation(record)
}
}
///|
/// Apply a ChildList mutation (add/remove children)
fn Document::apply_child_list_mutation(
self : Document,
record : @dom.MutationRecord,
) -> Unit {
let parent_dom_id = record.target.to_int()
// Handle added nodes
for added in record.added_nodes {
let child_dom_id = added.to_int()
// Create a new LayoutNode for the added DOM node
match self.create_layout_node_for_dom(child_dom_id) {
Some(layout_node) =>
// Find parent in layout tree
match self.dom_to_layout.get(parent_dom_id) {
Some(parent_uid) =>
// Get parent's string id
match self.layout.find_node(parent_uid) {
Some(parent) => {
// Add to parent's children
parent.children.push(layout_node)
// Register in maps
self.dom_to_layout[child_dom_id] = layout_node.uid
self.layout_to_dom[layout_node.uid] = child_dom_id
// Update layout tree maps
self.layout.node_map[layout_node.uid] = layout_node
self.layout.parent_map[layout_node.uid] = parent_uid
// Mark parent dirty
self.layout.mark_node_dirty(parent_uid)
}
None => ()
}
None =>
// Parent might be root (document node)
if parent_dom_id == 0 {
// Add to layout root
self.layout.root.children.push(layout_node)
self.dom_to_layout[child_dom_id] = layout_node.uid
self.layout_to_dom[layout_node.uid] = child_dom_id
self.layout.node_map[layout_node.uid] = layout_node
self.layout.parent_map[layout_node.uid] = self.layout.root.uid
self.layout.mark_node_dirty(self.layout.root.uid)
}
}
None => ()
}
}
// Handle removed nodes
for removed in record.removed_nodes {
let child_dom_id = removed.to_int()
match self.dom_to_layout.get(child_dom_id) {
Some(child_uid) =>
// Remove from layout tree
match self.layout.parent_map.get(child_uid) {
Some(parent_uid) =>
match self.layout.find_node(parent_uid) {
Some(parent) => {
// Find and remove child
let mut idx = -1
for i, child in parent.children {
if child.uid == child_uid {
idx = i
break
}
}
if idx >= 0 {
let _ = parent.children.remove(idx)
// Clean up maps
self.remove_from_maps(child_uid)
// Mark parent dirty
self.layout.mark_node_dirty(parent_uid)
}
}
None => ()
}
None => ()
}
None => ()
}
}
}
///|
/// Apply an Attributes mutation
fn Document::apply_attributes_mutation(
self : Document,
record : @dom.MutationRecord,
) -> Unit {
let dom_id = record.target.to_int()
match self.dom_to_layout.get(dom_id) {
Some(layout_uid) =>
// Attribute changes might affect style
match record.attribute_name {
Some("style") | Some("class") =>
// Would need to recompute CSS cascade
// For now, just mark dirty
self.layout.mark_node_dirty(layout_uid)
_ => ()
}
None => ()
}
}
///|
/// Apply a CharacterData mutation (text content change)
fn Document::apply_character_data_mutation(
self : Document,
record : @dom.MutationRecord,
) -> Unit {
let dom_id = record.target.to_int()
match self.dom_to_layout.get(dom_id) {
Some(layout_uid) =>
// Text content change affects intrinsic size
self.layout.mark_node_dirty(layout_uid)
None => ()
}
}
///|
/// Apply a StyleChange mutation
fn Document::apply_style_change_mutation(
self : Document,
record : @dom.MutationRecord,
) -> Unit {
let dom_id = record.target.to_int()
match self.dom_to_layout.get(dom_id) {
Some(layout_uid) =>
// Style changed - mark dirty
// In a full implementation, we would apply the specific property changes
self.layout.mark_node_dirty(layout_uid)
None => ()
}
}
///|
/// Create a LayoutNode for a DOM node
fn Document::create_layout_node_for_dom(
self : Document,
dom_id : Int,
) -> LayoutNode? {
let node_id = @dom.NodeId::from_int(dom_id)
match self.dom.get_node_info(node_id) {
Ok(info) => {
let layout_id = dom_id.to_string()
match info.node_type {
@dom.Element => {
let node = LayoutNode::create_with_id(layout_id)
// Get tag name for element-specific styling
match self.dom.get_tag_name(node_id) {
Ok(tag) =>
// Apply default styles based on tag
apply_default_tag_styles(node, tag)
Err(_) => ()
}
Some(node)
}
@dom.Text => {
// Text nodes get a measure function for intrinsic sizing
let node = LayoutNode::create_with_id(layout_id)
// In a full implementation, would set up text measurement
Some(node)
}
_ => None // Document, Comment nodes don't get layout nodes
}
}
Err(_) => None
}
}
///|
/// Remove a node and its descendants from the maps
fn Document::remove_from_maps(self : Document, uid : Int) -> Unit {
match self.layout.node_map.get(uid) {
Some(node) => {
// Remove children first
for child in node.children {
self.remove_from_maps(child.uid)
}
// Remove from layout maps
self.layout.node_map.remove(uid)
self.layout.parent_map.remove(uid)
// Remove from DOM<->Layout mapping
match self.layout_to_dom.get(uid) {
Some(dom_id) => {
self.dom_to_layout.remove(dom_id)
self.layout_to_dom.remove(uid)
}
None => ()
}
}
None => ()
}
}
///|
/// Apply default styles based on HTML tag
fn apply_default_tag_styles(node : LayoutNode, tag : String) -> Unit {
match tag.to_lower() {
"div"
| "section"
| "article"
| "main"
| "header"
| "footer"
| "nav"
| "aside" => node.style.display = @types.Block
"span" | "a" | "em" | "strong" | "b" | "i" =>
node.style.display = @types.Block // Simplified
"img" =>
// Images have intrinsic sizing
node.intrinsic_state = Some(
Pending(placeholder_width=300.0, placeholder_height=150.0),
)
_ => ()
}
}
// =============================================================================
// Convenience API for Document
// =============================================================================
///|
/// Create an element and add it to the document
pub fn Document::create_element(self : Document, tag : String) -> @dom.NodeId {
self.dom.create_element(tag)
}
///|
/// Create a text node
pub fn Document::create_text(self : Document, content : String) -> @dom.NodeId {
self.dom.create_text(content)
}
///|
/// Append a child to a parent
pub fn Document::append_child(
self : Document,
parent : @dom.NodeId,
child : @dom.NodeId,
) -> Result[Unit, @dom.CoreError] {
self.dom.append_child(parent, child)
}
///|
/// Set attribute on an element
pub fn Document::set_attribute(
self : Document,
node : @dom.NodeId,
name : String,
value : String,
) -> Result[Unit, @dom.CoreError] {
self.dom.set_attribute(node, name, value)
}
///|
/// Set text content
pub fn Document::set_text_content(
self : Document,
node : @dom.NodeId,
content : String,
) -> Result[Unit, @dom.CoreError] {
self.dom.set_text_content(node, content)
}
///|
/// Get document root
pub fn Document::get_document_root(self : Document) -> @dom.NodeId {
self.dom.get_document()
}
///|
/// Query selector
pub fn Document::query_selector(
self : Document,
selector : String,
) -> Result[@dom.NodeId?, @dom.CoreError] {
self.dom.query_selector(self.dom.get_document(), selector)
}
///|
/// One-shot: Process mutations and compute layout
pub fn Document::update(self : Document) -> @types.Layout {
let _ = self.process_mutations()
self.compute_layout()
}