use super::compute::compute_layout;
use super::node::{
ComputedLayout, Edges, FlexProps, GridProps, Inset, LayoutNode, LayoutSpacing, SizeConstraints,
};
use super::tree::LayoutTree;
use super::Rect;
use crate::dom::DomId;
use crate::style::Style;
use std::collections::HashMap;
#[derive(Debug, Clone, thiserror::Error)]
pub enum LayoutError {
#[error("Layout node not found for DOM ID {0}")]
NodeNotFound(u64),
#[error("Failed to create layout node: {0}")]
NodeCreationFailed(String),
#[error("Failed to update layout style: {0}")]
StyleUpdateFailed(String),
#[error("Failed to add child node: {0}")]
AddChildFailed(String),
#[error("Failed to remove layout node: {0}")]
RemoveFailed(String),
#[error("Failed to compute layout: {0}")]
ComputeFailed(String),
#[error("Failed to get layout result: {0}")]
LayoutRetrievalFailed(String),
}
pub type LayoutResult<T> = Result<T, LayoutError>;
pub struct LayoutEngine {
tree: LayoutTree,
nodes: HashMap<DomId, u64>,
}
impl LayoutEngine {
pub fn new() -> Self {
Self {
tree: LayoutTree::new(),
nodes: HashMap::new(),
}
}
pub fn create_node(&mut self, dom_id: DomId, style: &Style) -> LayoutResult<()> {
let node_id = dom_id.inner();
let node = style_to_layout_node(node_id, style);
self.tree.insert(node);
self.nodes.insert(dom_id, node_id);
Ok(())
}
pub fn create_node_with_children(
&mut self,
dom_id: DomId,
style: &Style,
children: &[DomId],
) -> LayoutResult<()> {
let node_id = dom_id.inner();
let mut node = style_to_layout_node(node_id, style);
let child_ids: Vec<u64> = children
.iter()
.filter_map(|id| self.nodes.get(id).copied())
.collect();
node.children = child_ids.clone();
self.tree.insert(node);
self.nodes.insert(dom_id, node_id);
for child_id in child_ids {
if let Some(child) = self.tree.get_mut(child_id) {
child.parent = Some(node_id);
}
}
Ok(())
}
pub fn update_style(&mut self, dom_id: DomId, style: &Style) -> LayoutResult<()> {
let node_id = dom_id.inner();
if let Some(node) = self.tree.get_mut(node_id) {
update_node_from_style(node, style);
Ok(())
} else {
Err(LayoutError::NodeNotFound(dom_id.inner()))
}
}
pub fn add_child(&mut self, parent_dom_id: DomId, child_dom_id: DomId) -> LayoutResult<()> {
let parent_id = parent_dom_id.inner();
let child_id = child_dom_id.inner();
if !self.nodes.contains_key(&parent_dom_id) {
return Err(LayoutError::NodeNotFound(parent_dom_id.inner()));
}
if !self.nodes.contains_key(&child_dom_id) {
return Err(LayoutError::NodeNotFound(child_dom_id.inner()));
}
self.tree.add_child(parent_id, child_id);
Ok(())
}
pub fn remove_node(&mut self, dom_id: DomId) -> LayoutResult<()> {
let node_id = dom_id.inner();
self.tree.remove(node_id);
self.nodes.remove(&dom_id);
Ok(())
}
pub fn compute(
&mut self,
root_dom_id: DomId,
available_width: u16,
available_height: u16,
) -> LayoutResult<()> {
let node_id = root_dom_id.inner();
if !self.tree.contains(node_id) {
return Err(LayoutError::NodeNotFound(root_dom_id.inner()));
}
compute_layout(&mut self.tree, node_id, available_width, available_height);
Ok(())
}
pub fn layout(&self, dom_id: DomId) -> LayoutResult<Rect> {
let node_id = dom_id.inner();
self.tree
.get(node_id)
.map(|node| Rect {
x: node.computed.x,
y: node.computed.y,
width: node.computed.width,
height: node.computed.height,
})
.ok_or_else(|| LayoutError::NodeNotFound(dom_id.inner()))
}
pub fn try_layout(&self, dom_id: DomId) -> Option<Rect> {
self.layout(dom_id).ok()
}
pub fn clear(&mut self) {
self.tree.clear();
self.nodes.clear();
}
}
impl Default for LayoutEngine {
fn default() -> Self {
Self::new()
}
}
fn style_to_layout_node(id: u64, style: &Style) -> LayoutNode {
LayoutNode {
id,
display: style.layout.display,
position: style.layout.position,
flex: FlexProps {
direction: style.layout.flex_direction,
justify_content: style.layout.justify_content,
align_items: style.layout.align_items,
flex_grow: style.layout.flex_grow,
gap: style.layout.gap,
column_gap: style.layout.column_gap,
row_gap: style.layout.row_gap,
},
grid: GridProps {
template_columns: style.layout.grid_template_columns.tracks.clone(),
template_rows: style.layout.grid_template_rows.tracks.clone(),
column: style.layout.grid_column,
row: style.layout.grid_row,
},
spacing: LayoutSpacing {
padding: Edges::from(style.spacing.padding),
margin: Edges::from(style.spacing.margin),
inset: Inset {
top: style.spacing.top,
right: style.spacing.right,
bottom: style.spacing.bottom,
left: style.spacing.left,
},
},
sizing: SizeConstraints {
width: style.sizing.width,
height: style.sizing.height,
min_width: style.sizing.min_width,
max_width: style.sizing.max_width,
min_height: style.sizing.min_height,
max_height: style.sizing.max_height,
},
children: Vec::new(),
parent: None,
computed: ComputedLayout::default(),
dirty: true,
}
}
fn update_node_from_style(node: &mut LayoutNode, style: &Style) {
node.display = style.layout.display;
node.position = style.layout.position;
node.flex = FlexProps {
direction: style.layout.flex_direction,
justify_content: style.layout.justify_content,
align_items: style.layout.align_items,
flex_grow: style.layout.flex_grow,
gap: style.layout.gap,
column_gap: style.layout.column_gap,
row_gap: style.layout.row_gap,
};
node.grid = GridProps {
template_columns: style.layout.grid_template_columns.tracks.clone(),
template_rows: style.layout.grid_template_rows.tracks.clone(),
column: style.layout.grid_column,
row: style.layout.grid_row,
};
node.spacing = LayoutSpacing {
padding: Edges::from(style.spacing.padding),
margin: Edges::from(style.spacing.margin),
inset: Inset {
top: style.spacing.top,
right: style.spacing.right,
bottom: style.spacing.bottom,
left: style.spacing.left,
},
};
node.sizing = SizeConstraints {
width: style.sizing.width,
height: style.sizing.height,
min_width: style.sizing.min_width,
max_width: style.sizing.max_width,
min_height: style.sizing.min_height,
max_height: style.sizing.max_height,
};
node.dirty = true;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::{Display, FlexDirection, Size};
#[test]
fn test_layout_engine_new() {
let engine = LayoutEngine::new();
assert!(engine.nodes.is_empty());
}
#[test]
fn test_create_node() {
let mut engine = LayoutEngine::new();
let style = Style::default();
let dom_id = DomId::new(1);
engine.create_node(dom_id, &style).unwrap();
assert!(engine.nodes.contains_key(&dom_id));
}
#[test]
fn test_create_multiple_nodes() {
let mut engine = LayoutEngine::new();
let style = Style::default();
let id1 = DomId::new(1);
let id2 = DomId::new(2);
let id3 = DomId::new(3);
engine.create_node(id1, &style).unwrap();
engine.create_node(id2, &style).unwrap();
engine.create_node(id3, &style).unwrap();
assert!(engine.nodes.contains_key(&id1));
assert!(engine.nodes.contains_key(&id2));
assert!(engine.nodes.contains_key(&id3));
}
#[test]
fn test_compute_layout_single_node() {
let mut engine = LayoutEngine::new();
let mut style = Style::default();
style.sizing.width = Size::Fixed(100);
style.sizing.height = Size::Fixed(50);
let id = DomId::new(1);
engine.create_node(id, &style).unwrap();
engine.compute(id, 200, 200).unwrap();
let layout = engine.layout(id).unwrap();
assert_eq!(layout.width, 200); assert_eq!(layout.height, 200);
}
#[test]
fn test_compute_layout_with_children() {
let mut engine = LayoutEngine::new();
let mut child_style = Style::default();
child_style.sizing.width = Size::Fixed(50);
child_style.sizing.height = Size::Fixed(30);
let child1 = DomId::new(1);
let child2 = DomId::new(2);
engine.create_node(child1, &child_style).unwrap();
engine.create_node(child2, &child_style).unwrap();
let mut parent_style = Style::default();
parent_style.layout.display = Display::Flex;
parent_style.layout.flex_direction = FlexDirection::Row;
parent_style.sizing.width = Size::Fixed(200);
parent_style.sizing.height = Size::Fixed(100);
let parent = DomId::new(3);
engine
.create_node_with_children(parent, &parent_style, &[child1, child2])
.unwrap();
engine.compute(parent, 300, 300).unwrap();
let parent_layout = engine.layout(parent).unwrap();
assert_eq!(parent_layout.width, 300);
assert_eq!(parent_layout.height, 300);
let child1_layout = engine.layout(child1).unwrap();
let child2_layout = engine.layout(child2).unwrap();
assert_eq!(child1_layout.x, 0);
assert_eq!(child2_layout.x, 50); }
#[test]
fn test_remove_node() {
let mut engine = LayoutEngine::new();
let style = Style::default();
let id = DomId::new(1);
engine.create_node(id, &style).unwrap();
assert!(engine.nodes.contains_key(&id));
engine.remove_node(id).unwrap();
assert!(!engine.nodes.contains_key(&id));
}
#[test]
fn test_clear() {
let mut engine = LayoutEngine::new();
let style = Style::default();
engine.create_node(DomId::new(1), &style).unwrap();
engine.create_node(DomId::new(2), &style).unwrap();
engine.create_node(DomId::new(3), &style).unwrap();
assert_eq!(engine.nodes.len(), 3);
engine.clear();
assert!(engine.nodes.is_empty());
}
#[test]
fn test_layout_error_node_not_found() {
let engine = LayoutEngine::new();
let result = engine.layout(DomId::new(999));
assert!(matches!(result, Err(LayoutError::NodeNotFound(_))));
}
#[test]
fn test_try_layout_returns_none_for_missing_node() {
let engine = LayoutEngine::new();
assert!(engine.try_layout(DomId::new(999)).is_none());
}
}