use crate::style::{
AlignItems, Display, FlexDirection, GridPlacement, GridTrack, JustifyContent, Position, Size,
Spacing,
};
#[derive(Debug, Clone)]
pub struct LayoutNode {
pub id: u64,
pub display: Display,
pub position: Position,
pub flex: FlexProps,
pub grid: GridProps,
pub spacing: LayoutSpacing,
pub sizing: SizeConstraints,
pub children: Vec<u64>,
pub parent: Option<u64>,
pub computed: ComputedLayout,
pub dirty: bool,
}
impl Default for LayoutNode {
fn default() -> Self {
Self {
id: 0,
display: Display::default(),
position: Position::default(),
flex: FlexProps::default(),
grid: GridProps::default(),
spacing: LayoutSpacing::default(),
sizing: SizeConstraints::default(),
children: Vec::new(),
parent: None,
computed: ComputedLayout::default(),
dirty: true, }
}
}
#[derive(Debug, Clone, Default)]
pub struct FlexProps {
pub direction: FlexDirection,
pub justify_content: JustifyContent,
pub align_items: AlignItems,
pub flex_grow: f32,
pub gap: u16,
pub column_gap: Option<u16>,
pub row_gap: Option<u16>,
}
#[allow(dead_code)]
impl FlexProps {
pub fn main_gap(&self) -> u16 {
match self.direction {
FlexDirection::Row => self.column_gap.unwrap_or(self.gap),
FlexDirection::Column => self.row_gap.unwrap_or(self.gap),
}
}
pub fn cross_gap(&self) -> u16 {
match self.direction {
FlexDirection::Row => self.row_gap.unwrap_or(self.gap),
FlexDirection::Column => self.column_gap.unwrap_or(self.gap),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GridProps {
pub template_columns: Vec<GridTrack>,
pub template_rows: Vec<GridTrack>,
pub column: GridPlacement,
pub row: GridPlacement,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LayoutSpacing {
pub padding: Edges,
pub margin: Edges,
pub inset: Inset,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Edges {
pub top: u16,
pub right: u16,
pub bottom: u16,
pub left: u16,
}
#[allow(dead_code)]
impl Edges {
pub fn horizontal(&self) -> u16 {
self.left.saturating_add(self.right)
}
pub fn vertical(&self) -> u16 {
self.top.saturating_add(self.bottom)
}
}
impl From<Spacing> for Edges {
fn from(s: Spacing) -> Self {
Self {
top: s.top,
right: s.right,
bottom: s.bottom,
left: s.left,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Inset {
pub top: Option<i16>,
pub right: Option<i16>,
pub bottom: Option<i16>,
pub left: Option<i16>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SizeConstraints {
pub width: Size,
pub height: Size,
pub min_width: Size,
pub max_width: Size,
pub min_height: Size,
pub max_height: Size,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ComputedLayout {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
#[allow(dead_code)]
impl ComputedLayout {
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn content_width(&self, padding: &Edges) -> u16 {
self.width
.saturating_sub(padding.left)
.saturating_sub(padding.right)
}
pub fn content_height(&self, padding: &Edges) -> u16 {
self.height
.saturating_sub(padding.top)
.saturating_sub(padding.bottom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edges_from_spacing() {
let spacing = Spacing::new(1, 2, 3, 4);
let edges: Edges = spacing.into();
assert_eq!(edges.top, 1);
assert_eq!(edges.right, 2);
assert_eq!(edges.bottom, 3);
assert_eq!(edges.left, 4);
}
#[test]
fn test_edges_horizontal_vertical() {
let edges = Edges {
top: 5,
right: 10,
bottom: 15,
left: 20,
};
assert_eq!(edges.horizontal(), 30);
assert_eq!(edges.vertical(), 20);
}
#[test]
fn test_flex_props_gap() {
let mut props = FlexProps::default();
props.gap = 5;
props.direction = FlexDirection::Row;
assert_eq!(props.main_gap(), 5);
assert_eq!(props.cross_gap(), 5);
props.column_gap = Some(10);
assert_eq!(props.main_gap(), 10); assert_eq!(props.cross_gap(), 5);
props.direction = FlexDirection::Column;
assert_eq!(props.main_gap(), 5); assert_eq!(props.cross_gap(), 10);
}
#[test]
fn test_computed_layout_content() {
let layout = ComputedLayout::new(0, 0, 100, 50);
let padding = Edges {
top: 5,
right: 10,
bottom: 5,
left: 10,
};
assert_eq!(layout.content_width(&padding), 80);
assert_eq!(layout.content_height(&padding), 40);
}
#[test]
fn test_layout_node_default() {
let node = LayoutNode::default();
assert_eq!(node.id, 0);
assert!(node.children.is_empty());
assert!(node.parent.is_none());
}
#[test]
fn test_layout_node_with_children() {
let mut node = LayoutNode::default();
node.id = 1;
node.children = vec![2, 3, 4];
assert_eq!(node.children.len(), 3);
assert!(node.children.contains(&2));
}
#[test]
fn test_layout_node_with_parent() {
let mut node = LayoutNode::default();
node.id = 5;
node.parent = Some(1);
assert_eq!(node.parent, Some(1));
}
#[test]
fn test_layout_node_clone() {
let mut node = LayoutNode::default();
node.id = 10;
node.children = vec![20, 30];
let cloned = node.clone();
assert_eq!(cloned.id, 10);
assert_eq!(cloned.children, vec![20, 30]);
}
#[test]
fn test_flex_props_default() {
let props = FlexProps::default();
assert_eq!(props.gap, 0);
assert_eq!(props.column_gap, None);
assert_eq!(props.row_gap, None);
}
#[test]
fn test_flex_props_main_gap_row() {
let mut props = FlexProps::default();
props.direction = FlexDirection::Row;
props.gap = 8;
assert_eq!(props.main_gap(), 8);
props.column_gap = Some(16);
assert_eq!(props.main_gap(), 16);
}
#[test]
fn test_flex_props_main_gap_column() {
let mut props = FlexProps::default();
props.direction = FlexDirection::Column;
props.gap = 8;
assert_eq!(props.main_gap(), 8);
props.row_gap = Some(12);
assert_eq!(props.main_gap(), 12);
}
#[test]
fn test_flex_props_cross_gap_row() {
let mut props = FlexProps::default();
props.direction = FlexDirection::Row;
props.gap = 8;
assert_eq!(props.cross_gap(), 8);
props.row_gap = Some(4);
assert_eq!(props.cross_gap(), 4);
}
#[test]
fn test_flex_props_cross_gap_column() {
let mut props = FlexProps::default();
props.direction = FlexDirection::Column;
props.gap = 8;
assert_eq!(props.cross_gap(), 8);
props.column_gap = Some(20);
assert_eq!(props.cross_gap(), 20);
}
#[test]
fn test_grid_props_default() {
let props = GridProps::default();
assert!(props.template_columns.is_empty());
assert!(props.template_rows.is_empty());
}
#[test]
fn test_grid_props_clone() {
let props = GridProps::default();
let cloned = props.clone();
assert!(cloned.template_columns.is_empty());
}
#[test]
fn test_layout_spacing_default() {
let spacing = LayoutSpacing::default();
assert_eq!(spacing.padding.top, 0);
assert_eq!(spacing.margin.left, 0);
}
#[test]
fn test_layout_spacing_copy() {
let spacing = LayoutSpacing {
padding: Edges {
top: 1,
right: 2,
bottom: 3,
left: 4,
},
margin: Edges::default(),
inset: Inset::default(),
};
let copied = spacing;
assert_eq!(copied.padding.top, 1);
}
#[test]
fn test_edges_default() {
let edges = Edges::default();
assert_eq!(edges.top, 0);
assert_eq!(edges.right, 0);
assert_eq!(edges.bottom, 0);
assert_eq!(edges.left, 0);
}
#[test]
fn test_edges_horizontal() {
let edges = Edges {
top: 0,
right: 15,
bottom: 0,
left: 5,
};
assert_eq!(edges.horizontal(), 20);
}
#[test]
fn test_edges_vertical() {
let edges = Edges {
top: 10,
right: 0,
bottom: 20,
left: 0,
};
assert_eq!(edges.vertical(), 30);
}
#[test]
fn test_edges_horizontal_saturating() {
let edges = Edges {
top: 0,
right: u16::MAX,
bottom: 0,
left: 100,
};
assert_eq!(edges.horizontal(), u16::MAX);
}
#[test]
fn test_edges_vertical_saturating() {
let edges = Edges {
top: u16::MAX,
right: 0,
bottom: 50,
left: 0,
};
assert_eq!(edges.vertical(), u16::MAX);
}
#[test]
fn test_edges_equality() {
let e1 = Edges {
top: 1,
right: 2,
bottom: 3,
left: 4,
};
let e2 = Edges {
top: 1,
right: 2,
bottom: 3,
left: 4,
};
let e3 = Edges {
top: 0,
right: 0,
bottom: 0,
left: 0,
};
assert_eq!(e1, e2);
assert_ne!(e1, e3);
}
#[test]
fn test_inset_default() {
let inset = Inset::default();
assert_eq!(inset.top, None);
assert_eq!(inset.right, None);
assert_eq!(inset.bottom, None);
assert_eq!(inset.left, None);
}
#[test]
fn test_inset_with_values() {
let inset = Inset {
top: Some(10),
right: Some(-5),
bottom: None,
left: Some(20),
};
assert_eq!(inset.top, Some(10));
assert_eq!(inset.right, Some(-5));
assert_eq!(inset.bottom, None);
assert_eq!(inset.left, Some(20));
}
#[test]
fn test_inset_copy() {
let inset = Inset {
top: Some(5),
right: None,
bottom: None,
left: None,
};
let copied = inset;
assert_eq!(copied.top, Some(5));
}
#[test]
fn test_size_constraints_default() {
let constraints = SizeConstraints::default();
assert_eq!(constraints.width, Size::Auto);
assert_eq!(constraints.height, Size::Auto);
}
#[test]
fn test_size_constraints_copy() {
let constraints = SizeConstraints {
width: Size::Fixed(100),
height: Size::Fixed(50),
..Default::default()
};
let copied = constraints;
assert_eq!(copied.width, Size::Fixed(100));
}
#[test]
fn test_computed_layout_default() {
let layout = ComputedLayout::default();
assert_eq!(layout.x, 0);
assert_eq!(layout.y, 0);
assert_eq!(layout.width, 0);
assert_eq!(layout.height, 0);
}
#[test]
fn test_computed_layout_new() {
let layout = ComputedLayout::new(10, 20, 100, 50);
assert_eq!(layout.x, 10);
assert_eq!(layout.y, 20);
assert_eq!(layout.width, 100);
assert_eq!(layout.height, 50);
}
#[test]
fn test_computed_layout_content_width() {
let layout = ComputedLayout::new(0, 0, 80, 40);
let padding = Edges {
top: 0,
right: 10,
bottom: 0,
left: 10,
};
assert_eq!(layout.content_width(&padding), 60);
}
#[test]
fn test_computed_layout_content_height() {
let layout = ComputedLayout::new(0, 0, 80, 40);
let padding = Edges {
top: 5,
right: 0,
bottom: 5,
left: 0,
};
assert_eq!(layout.content_height(&padding), 30);
}
#[test]
fn test_computed_layout_content_saturating() {
let layout = ComputedLayout::new(0, 0, 10, 10);
let padding = Edges {
top: 20,
right: 30,
bottom: 20,
left: 30,
};
assert_eq!(layout.content_width(&padding), 0);
assert_eq!(layout.content_height(&padding), 0);
}
#[test]
fn test_computed_layout_equality() {
let l1 = ComputedLayout::new(0, 0, 100, 50);
let l2 = ComputedLayout::new(0, 0, 100, 50);
let l3 = ComputedLayout::new(10, 10, 100, 50);
assert_eq!(l1, l2);
assert_ne!(l1, l3);
}
#[test]
fn test_computed_layout_copy() {
let layout = ComputedLayout::new(5, 10, 200, 100);
let copied = layout;
assert_eq!(copied.x, 5);
assert_eq!(copied.y, 10);
}
}