orbit/
layout.rs

1//! Advanced Layout Engine for Orbit UI Framework
2//!
3//! This module provides a comprehensive layout system with:
4//! - Flexbox-compatible layout properties
5//! - Constraint-based layout calculation
6//! - Performance optimizations with incremental updates
7//! - Integration with the component system
8
9use std::collections::HashMap;
10use std::fmt;
11
12use crate::component::ComponentId;
13
14/// Represents a 2D point with x and y coordinates
15#[derive(Debug, Clone, Copy, PartialEq)]
16pub struct Point {
17    pub x: f32,
18    pub y: f32,
19}
20
21impl Point {
22    pub fn new(x: f32, y: f32) -> Self {
23        Self { x, y }
24    }
25
26    pub fn zero() -> Self {
27        Self::new(0.0, 0.0)
28    }
29}
30
31/// Represents a 2D size with width and height
32#[derive(Debug, Clone, Copy, PartialEq)]
33pub struct Size {
34    pub width: f32,
35    pub height: f32,
36}
37
38impl Size {
39    pub fn new(width: f32, height: f32) -> Self {
40        Self { width, height }
41    }
42
43    pub fn zero() -> Self {
44        Self::new(0.0, 0.0)
45    }
46
47    pub fn area(&self) -> f32 {
48        self.width * self.height
49    }
50}
51
52/// Represents a rectangle with position and size
53#[derive(Debug, Clone, Copy, PartialEq)]
54pub struct Rect {
55    pub origin: Point,
56    pub size: Size,
57}
58
59impl Rect {
60    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
61        Self {
62            origin: Point::new(x, y),
63            size: Size::new(width, height),
64        }
65    }
66
67    pub fn zero() -> Self {
68        Self {
69            origin: Point::zero(),
70            size: Size::zero(),
71        }
72    }
73
74    pub fn x(&self) -> f32 {
75        self.origin.x
76    }
77
78    pub fn y(&self) -> f32 {
79        self.origin.y
80    }
81
82    pub fn width(&self) -> f32 {
83        self.size.width
84    }
85
86    pub fn height(&self) -> f32 {
87        self.size.height
88    }
89
90    pub fn max_x(&self) -> f32 {
91        self.origin.x + self.size.width
92    }
93
94    pub fn max_y(&self) -> f32 {
95        self.origin.y + self.size.height
96    }
97
98    pub fn contains_point(&self, point: Point) -> bool {
99        point.x >= self.x()
100            && point.x <= self.max_x()
101            && point.y >= self.y()
102            && point.y <= self.max_y()
103    }
104}
105
106/// Flex direction determines the main axis
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108pub enum FlexDirection {
109    #[default]
110    Row,
111    Column,
112    RowReverse,
113    ColumnReverse,
114}
115
116/// Flex wrap determines whether items wrap to new lines
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
118pub enum FlexWrap {
119    #[default]
120    NoWrap,
121    Wrap,
122    WrapReverse,
123}
124
125/// Justify content controls alignment along the main axis
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
127pub enum JustifyContent {
128    #[default]
129    FlexStart,
130    FlexEnd,
131    Center,
132    SpaceBetween,
133    SpaceAround,
134    SpaceEvenly,
135}
136
137/// Align items controls alignment along the cross axis
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
139pub enum AlignItems {
140    FlexStart,
141    FlexEnd,
142    Center,
143    #[default]
144    Stretch,
145    Baseline,
146}
147
148/// Align content controls alignment of wrapped lines
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
150pub enum AlignContent {
151    FlexStart,
152    FlexEnd,
153    Center,
154    SpaceBetween,
155    SpaceAround,
156    #[default]
157    Stretch,
158}
159
160/// Dimension value can be auto, fixed, or percentage
161#[derive(Debug, Clone, Copy, PartialEq, Default)]
162pub enum Dimension {
163    #[default]
164    Auto,
165    Points(f32),
166    Percent(f32),
167}
168
169impl Dimension {
170    /// Calculate actual value based on container size
171    pub fn resolve(&self, container_size: f32) -> f32 {
172        match self {
173            Dimension::Auto => 0.0, // Will be calculated during layout
174            Dimension::Points(points) => *points,
175            Dimension::Percent(percent) => container_size * percent / 100.0,
176        }
177    }
178}
179
180/// Edge values for margin, padding, border
181#[derive(Debug, Clone, Copy, PartialEq)]
182pub struct EdgeValues {
183    pub top: f32,
184    pub right: f32,
185    pub bottom: f32,
186    pub left: f32,
187}
188
189impl EdgeValues {
190    pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
191        Self {
192            top,
193            right,
194            bottom,
195            left,
196        }
197    }
198
199    pub fn uniform(value: f32) -> Self {
200        Self::new(value, value, value, value)
201    }
202
203    pub fn horizontal_vertical(horizontal: f32, vertical: f32) -> Self {
204        Self::new(vertical, horizontal, vertical, horizontal)
205    }
206
207    pub fn zero() -> Self {
208        Self::uniform(0.0)
209    }
210
211    pub fn horizontal(&self) -> f32 {
212        self.left + self.right
213    }
214
215    pub fn vertical(&self) -> f32 {
216        self.top + self.bottom
217    }
218}
219
220impl Default for EdgeValues {
221    fn default() -> Self {
222        Self::zero()
223    }
224}
225
226/// Layout style properties for a node
227#[derive(Debug, Clone, PartialEq)]
228pub struct LayoutStyle {
229    // Position
230    pub position_type: PositionType,
231    pub top: Dimension,
232    pub right: Dimension,
233    pub bottom: Dimension,
234    pub left: Dimension,
235
236    // Size
237    pub width: Dimension,
238    pub height: Dimension,
239    pub min_width: Dimension,
240    pub min_height: Dimension,
241    pub max_width: Dimension,
242    pub max_height: Dimension,
243
244    // Flexbox container properties
245    pub flex_direction: FlexDirection,
246    pub flex_wrap: FlexWrap,
247    pub justify_content: JustifyContent,
248    pub align_items: AlignItems,
249    pub align_content: AlignContent,
250
251    // Flexbox item properties
252    pub flex_grow: f32,
253    pub flex_shrink: f32,
254    pub flex_basis: Dimension,
255    pub align_self: Option<AlignItems>,
256
257    // Spacing
258    pub margin: EdgeValues,
259    pub padding: EdgeValues,
260    pub border: EdgeValues, // Gap
261    pub gap: Gap,
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
265pub enum PositionType {
266    #[default]
267    Relative,
268    Absolute,
269}
270
271impl Default for LayoutStyle {
272    fn default() -> Self {
273        Self {
274            position_type: PositionType::default(),
275            top: Dimension::default(),
276            right: Dimension::default(),
277            bottom: Dimension::default(),
278            left: Dimension::default(),
279            width: Dimension::default(),
280            height: Dimension::default(),
281            min_width: Dimension::default(),
282            min_height: Dimension::default(),
283            max_width: Dimension::default(),
284            max_height: Dimension::default(),
285            flex_direction: FlexDirection::default(),
286            flex_wrap: FlexWrap::default(),
287            justify_content: JustifyContent::default(),
288            align_items: AlignItems::default(),
289            align_content: AlignContent::default(),
290            flex_grow: 0.0,
291            flex_shrink: 1.0,
292            flex_basis: Dimension::Auto,
293            align_self: None,
294            margin: EdgeValues::default(),
295            padding: EdgeValues::default(),
296            border: EdgeValues::default(),
297            gap: Gap::default(),
298        }
299    }
300}
301
302/// Computed layout result for a node
303#[derive(Debug, Clone, PartialEq)]
304pub struct LayoutResult {
305    /// Final position and size
306    pub rect: Rect,
307    /// Content area (excluding padding and border)
308    pub content_rect: Rect,
309    /// Whether this layout is dirty and needs recalculation
310    pub is_dirty: bool,
311}
312
313impl Default for LayoutResult {
314    fn default() -> Self {
315        Self {
316            rect: Rect::zero(),
317            content_rect: Rect::zero(),
318            is_dirty: true,
319        }
320    }
321}
322
323/// A node in the layout tree
324#[derive(Debug)]
325pub struct LayoutNode {
326    /// Unique identifier for this layout node
327    pub id: ComponentId,
328    /// Layout style properties
329    pub style: LayoutStyle,
330    /// Computed layout result
331    pub layout: LayoutResult,
332    /// Child nodes
333    pub children: Vec<LayoutNode>,
334    /// Parent node ID (if any)
335    pub parent_id: Option<ComponentId>,
336}
337
338impl LayoutNode {
339    /// Create a new layout node
340    pub fn new(id: ComponentId, style: LayoutStyle) -> Self {
341        Self {
342            id,
343            style,
344            layout: LayoutResult::default(),
345            children: Vec::new(),
346            parent_id: None,
347        }
348    }
349
350    /// Add a child node
351    pub fn add_child(&mut self, mut child: LayoutNode) {
352        child.parent_id = Some(self.id);
353        self.children.push(child);
354        self.mark_dirty();
355    }
356
357    /// Remove a child node by ID
358    pub fn remove_child(&mut self, child_id: ComponentId) -> Option<LayoutNode> {
359        if let Some(index) = self.children.iter().position(|child| child.id == child_id) {
360            let mut child = self.children.remove(index);
361            child.parent_id = None;
362            self.mark_dirty();
363            Some(child)
364        } else {
365            None
366        }
367    }
368
369    /// Mark this node and all ancestors as dirty
370    pub fn mark_dirty(&mut self) {
371        self.layout.is_dirty = true;
372        // In a real implementation, we'd traverse up to mark ancestors dirty
373    }
374
375    /// Check if this node needs layout recalculation
376    pub fn is_dirty(&self) -> bool {
377        self.layout.is_dirty || self.children.iter().any(|child| child.is_dirty())
378    }
379
380    /// Get the main axis size based on flex direction
381    pub fn main_axis_size(&self) -> f32 {
382        match self.style.flex_direction {
383            FlexDirection::Row | FlexDirection::RowReverse => self.layout.rect.width(),
384            FlexDirection::Column | FlexDirection::ColumnReverse => self.layout.rect.height(),
385        }
386    }
387
388    /// Get the cross axis size based on flex direction
389    pub fn cross_axis_size(&self) -> f32 {
390        match self.style.flex_direction {
391            FlexDirection::Row | FlexDirection::RowReverse => self.layout.rect.height(),
392            FlexDirection::Column | FlexDirection::ColumnReverse => self.layout.rect.width(),
393        }
394    }
395}
396
397/// Layout engine responsible for computing layouts
398#[derive(Debug)]
399pub struct LayoutEngine {
400    /// Cache of computed layouts for performance
401    layout_cache: HashMap<ComponentId, LayoutResult>,
402    /// Performance metrics
403    pub stats: LayoutStats,
404}
405
406/// Performance statistics for the layout engine
407#[derive(Debug, Default, Clone)]
408pub struct LayoutStats {
409    /// Number of layout calculations performed
410    pub layout_calculations: u64,
411    /// Time spent in layout calculations (microseconds)
412    pub layout_time_us: u64,
413    /// Number of cache hits
414    pub cache_hits: u64,
415    /// Number of cache misses
416    pub cache_misses: u64,
417    /// Number of nodes in the current layout tree
418    pub node_count: u32,
419}
420
421impl LayoutEngine {
422    /// Create a new layout engine
423    pub fn new() -> Self {
424        Self {
425            layout_cache: HashMap::new(),
426            stats: LayoutStats::default(),
427        }
428    }
429
430    /// Calculate layout for a node tree
431    pub fn calculate_layout(
432        &mut self,
433        root: &mut LayoutNode,
434        container_size: Size,
435    ) -> Result<(), LayoutError> {
436        let start_time = std::time::Instant::now();
437
438        // Clear dirty flags and prepare for layout
439        self.prepare_layout(root);
440
441        // Perform the actual layout calculation
442        self.layout_node(root, container_size)?;
443
444        // Update statistics
445        let elapsed = start_time.elapsed();
446        self.stats.layout_calculations += 1;
447        self.stats.layout_time_us += elapsed.as_micros() as u64;
448
449        Ok(())
450    }
451
452    /// Prepare the layout tree for calculation
453    fn prepare_layout(&mut self, node: &mut LayoutNode) {
454        if node.layout.is_dirty {
455            // Remove from cache if dirty
456            self.layout_cache.remove(&node.id);
457        }
458
459        // Count nodes for statistics
460        self.stats.node_count = self.count_nodes(node);
461
462        // Recursively prepare children
463        for child in &mut node.children {
464            self.prepare_layout(child);
465        }
466    }
467
468    /// Count total nodes in the tree
469    #[allow(clippy::only_used_in_recursion)]
470    fn count_nodes(&self, node: &LayoutNode) -> u32 {
471        1 + node
472            .children
473            .iter()
474            .map(|child| self.count_nodes(child))
475            .sum::<u32>()
476    }
477
478    /// Layout a single node and its children
479    fn layout_node(
480        &mut self,
481        node: &mut LayoutNode,
482        container_size: Size,
483    ) -> Result<(), LayoutError> {
484        // Check cache first
485        if !node.layout.is_dirty {
486            if let Some(cached_layout) = self.layout_cache.get(&node.id) {
487                node.layout = cached_layout.clone();
488                self.stats.cache_hits += 1;
489                return Ok(());
490            }
491        }
492
493        self.stats.cache_misses += 1;
494
495        // Calculate this node's size and position
496        self.calculate_node_size(node, container_size)?;
497        self.calculate_node_position(node)?;
498
499        // Layout children using flexbox algorithm
500        if !node.children.is_empty() {
501            self.layout_flex_children(node)?;
502        }
503
504        // Mark as clean and cache the result
505        node.layout.is_dirty = false;
506        self.layout_cache.insert(node.id, node.layout.clone());
507
508        Ok(())
509    }
510
511    /// Calculate the size of a node
512    fn calculate_node_size(
513        &self,
514        node: &mut LayoutNode,
515        container_size: Size,
516    ) -> Result<(), LayoutError> {
517        let style = &node.style;
518
519        // Calculate width
520        let width = match style.width {
521            Dimension::Auto => {
522                // Auto width will be determined by children or content
523                // For now, use container width minus margins and padding
524                container_size.width - style.margin.horizontal() - style.padding.horizontal()
525            }
526            _ => style.width.resolve(container_size.width),
527        };
528
529        // Calculate height
530        let height = match style.height {
531            Dimension::Auto => {
532                // Auto height will be determined by children or content
533                // For now, use a minimal height
534                0.0
535            }
536            _ => style.height.resolve(container_size.height),
537        };
538
539        // Apply min/max constraints (only if not Auto)
540        let final_width = {
541            let mut w = width;
542            if !matches!(style.min_width, Dimension::Auto) {
543                w = w.max(style.min_width.resolve(container_size.width));
544            }
545            if !matches!(style.max_width, Dimension::Auto) {
546                w = w.min(style.max_width.resolve(container_size.width));
547            }
548            w
549        };
550
551        let final_height = {
552            let mut h = height;
553            if !matches!(style.min_height, Dimension::Auto) {
554                h = h.max(style.min_height.resolve(container_size.height));
555            }
556            if !matches!(style.max_height, Dimension::Auto) {
557                h = h.min(style.max_height.resolve(container_size.height));
558            }
559            h
560        };
561
562        // Set the node's size
563        node.layout.rect.size = Size::new(final_width, final_height);
564
565        // Calculate content rect (excluding padding and border)
566        let content_x = node.layout.rect.x() + style.padding.left + style.border.left;
567        let content_y = node.layout.rect.y() + style.padding.top + style.border.top;
568        let content_width = final_width - style.padding.horizontal() - style.border.horizontal();
569        let content_height = final_height - style.padding.vertical() - style.border.vertical();
570
571        node.layout.content_rect = Rect::new(content_x, content_y, content_width, content_height);
572
573        Ok(())
574    }
575
576    /// Calculate the position of a node
577    fn calculate_node_position(&self, node: &mut LayoutNode) -> Result<(), LayoutError> {
578        let style = &node.style;
579
580        match style.position_type {
581            PositionType::Relative => {
582                // Position will be set by parent's layout algorithm
583                // For now, keep current position
584            }
585            PositionType::Absolute => {
586                // Position relative to containing block
587                let x = style.left.resolve(0.0); // TODO: Use actual containing block size
588                let y = style.top.resolve(0.0);
589                node.layout.rect.origin = Point::new(x, y);
590            }
591        }
592
593        Ok(())
594    }
595    /// Layout children using flexbox algorithm
596    fn layout_flex_children(&mut self, parent: &mut LayoutNode) -> Result<(), LayoutError> {
597        if parent.children.is_empty() {
598            return Ok(());
599        }
600
601        let parent_content_size = parent.layout.content_rect.size;
602        let parent_style = &parent.style;
603
604        // Separate absolutely positioned children
605        let (absolute_children, relative_children): (Vec<_>, Vec<_>) = (0..parent.children.len())
606            .partition(|&i| parent.children[i].style.position_type == PositionType::Absolute);
607
608        // Layout relatively positioned children with flexbox
609        if !relative_children.is_empty() {
610            // Check if wrapping is enabled
611            if parent_style.flex_wrap == FlexWrap::Wrap
612                || parent_style.flex_wrap == FlexWrap::WrapReverse
613            {
614                self.layout_flex_multiline(
615                    &mut parent.children,
616                    &relative_children,
617                    parent_content_size,
618                    parent_style,
619                )?;
620            } else {
621                self.layout_flex_line(
622                    &mut parent.children,
623                    &relative_children,
624                    parent_content_size,
625                    parent_style,
626                )?;
627            }
628        }
629
630        // Layout absolutely positioned children
631        for &child_index in &absolute_children {
632            let child = &mut parent.children[child_index];
633            self.layout_node(child, parent_content_size)?;
634        }
635
636        Ok(())
637    }
638    /// Layout a single flex line
639    fn layout_flex_line(
640        &mut self,
641        children: &mut [LayoutNode],
642        child_indices: &[usize],
643        container_size: Size,
644        parent_style: &LayoutStyle,
645    ) -> Result<(), LayoutError> {
646        let flex_direction = parent_style.flex_direction;
647        let is_row = matches!(
648            flex_direction,
649            FlexDirection::Row | FlexDirection::RowReverse
650        );
651        let is_reverse = matches!(
652            flex_direction,
653            FlexDirection::RowReverse | FlexDirection::ColumnReverse
654        );
655
656        let main_axis_size = if is_row {
657            container_size.width
658        } else {
659            container_size.height
660        };
661        let cross_axis_size = if is_row {
662            container_size.height
663        } else {
664            container_size.width
665        };
666
667        // First pass: Calculate item sizes and determine if we need to grow/shrink
668        let mut item_data = Vec::new();
669        let mut total_basis_size = 0.0;
670        let mut total_flex_grow = 0.0;
671        let mut total_flex_shrink = 0.0;
672
673        for &child_index in child_indices {
674            let child = &children[child_index];
675
676            // Calculate basis size
677            let basis_size = self.calculate_flex_basis_size(child, container_size, is_row)?;
678            let main_size = self.resolve_main_size(child, basis_size, is_row);
679            let cross_size = self.resolve_cross_size(child, cross_axis_size, is_row);
680
681            total_basis_size += main_size;
682            total_flex_grow += child.style.flex_grow;
683            total_flex_shrink += child.style.flex_shrink;
684
685            item_data.push(FlexItemData {
686                index: child_index,
687                basis_size,
688                main_size,
689                cross_size,
690                flex_grow: child.style.flex_grow,
691                flex_shrink: child.style.flex_shrink,
692            });
693        }
694
695        // Add gap sizes to total
696        let gap_size = if is_row {
697            parent_style.gap.column
698        } else {
699            parent_style.gap.row
700        };
701        let total_gaps = gap_size * (child_indices.len() as f32 - 1.0).max(0.0);
702        let available_space = main_axis_size - total_basis_size - total_gaps;
703
704        // Second pass: Distribute available space
705        self.distribute_flex_space(
706            &mut item_data,
707            available_space,
708            total_flex_grow,
709            total_flex_shrink,
710        )?;
711
712        // Third pass: Apply justify-content for positioning
713        let positions = self.calculate_justify_content_positions(
714            &item_data,
715            main_axis_size,
716            gap_size,
717            parent_style.justify_content,
718            is_reverse,
719        );
720
721        // Fourth pass: Apply cross-axis alignment and set final positions
722        for (i, &child_index) in child_indices.iter().enumerate() {
723            let child = &mut children[child_index];
724            let item = &item_data[i];
725            let main_pos = positions[i];
726
727            // Calculate cross-axis position
728            let cross_pos = self.calculate_cross_axis_position(
729                child,
730                item.cross_size,
731                cross_axis_size,
732                parent_style.align_items,
733            );
734
735            // Set final layout
736            if is_row {
737                child.layout.rect = Rect::new(main_pos, cross_pos, item.main_size, item.cross_size);
738            } else {
739                child.layout.rect = Rect::new(cross_pos, main_pos, item.cross_size, item.main_size);
740            }
741
742            // Recursively layout this child
743            self.layout_node(child, child.layout.rect.size)?;
744        }
745
746        Ok(())
747    }
748    /// Clear the layout cache
749    pub fn clear_cache(&mut self) {
750        self.layout_cache.clear();
751    }
752
753    /// Get layout statistics
754    pub fn get_stats(&self) -> &LayoutStats {
755        &self.stats
756    }
757
758    /// Reset layout statistics
759    pub fn reset_stats(&mut self) {
760        self.stats = LayoutStats::default();
761    }
762
763    // Helper methods for enhanced flexbox support
764
765    /// Calculate flex basis size for an item
766    fn calculate_flex_basis_size(
767        &self,
768        child: &LayoutNode,
769        container_size: Size,
770        is_row: bool,
771    ) -> Result<f32, LayoutError> {
772        let basis_size = match child.style.flex_basis {
773            Dimension::Auto => {
774                // Use main axis size if available, otherwise content size
775                if is_row {
776                    child.style.width.resolve(container_size.width)
777                } else {
778                    child.style.height.resolve(container_size.height)
779                }
780            }
781            _ => {
782                let main_axis_size = if is_row {
783                    container_size.width
784                } else {
785                    container_size.height
786                };
787                child.style.flex_basis.resolve(main_axis_size)
788            }
789        };
790        Ok(basis_size)
791    }
792
793    /// Resolve main axis size for an item
794    fn resolve_main_size(&self, child: &LayoutNode, basis_size: f32, is_row: bool) -> f32 {
795        let explicit_size = if is_row {
796            match child.style.width {
797                Dimension::Auto => basis_size,
798                _ => child.style.width.resolve(0.0), // Will be resolved properly in context
799            }
800        } else {
801            match child.style.height {
802                Dimension::Auto => basis_size,
803                _ => child.style.height.resolve(0.0),
804            }
805        };
806        explicit_size.max(basis_size)
807    }
808
809    /// Resolve cross axis size for an item
810    fn resolve_cross_size(&self, child: &LayoutNode, cross_axis_size: f32, is_row: bool) -> f32 {
811        if is_row {
812            match child.style.height {
813                Dimension::Auto => cross_axis_size, // Will stretch by default
814                _ => child.style.height.resolve(cross_axis_size),
815            }
816        } else {
817            match child.style.width {
818                Dimension::Auto => cross_axis_size,
819                _ => child.style.width.resolve(cross_axis_size),
820            }
821        }
822    }
823
824    /// Distribute available space among flex items
825    fn distribute_flex_space(
826        &self,
827        items: &mut [FlexItemData],
828        available_space: f32,
829        total_flex_grow: f32,
830        total_flex_shrink: f32,
831    ) -> Result<(), LayoutError> {
832        if available_space > 0.0 && total_flex_grow > 0.0 {
833            // Distribute extra space proportionally
834            for item in items.iter_mut() {
835                if item.flex_grow > 0.0 {
836                    let growth = available_space * (item.flex_grow / total_flex_grow);
837                    item.main_size += growth;
838                }
839            }
840        } else if available_space < 0.0 && total_flex_shrink > 0.0 {
841            // Shrink items proportionally
842            for item in items.iter_mut() {
843                if item.flex_shrink > 0.0 {
844                    let shrinkage = available_space * (item.flex_shrink / total_flex_shrink);
845                    item.main_size = (item.main_size + shrinkage).max(0.0);
846                }
847            }
848        }
849        Ok(())
850    }
851
852    /// Calculate justify-content positions for items
853    fn calculate_justify_content_positions(
854        &self,
855        items: &[FlexItemData],
856        container_size: f32,
857        gap_size: f32,
858        justify_content: JustifyContent,
859        is_reverse: bool,
860    ) -> Vec<f32> {
861        let mut positions = Vec::with_capacity(items.len());
862
863        if items.is_empty() {
864            return positions;
865        }
866
867        let total_item_size: f32 = items.iter().map(|item| item.main_size).sum();
868        let total_gaps = gap_size * (items.len() as f32 - 1.0).max(0.0);
869        let remaining_space = container_size - total_item_size - total_gaps;
870
871        match justify_content {
872            JustifyContent::FlexStart => {
873                let mut current_pos = 0.0;
874                for item in items {
875                    positions.push(current_pos);
876                    current_pos += item.main_size + gap_size;
877                }
878            }
879            JustifyContent::FlexEnd => {
880                let mut current_pos = remaining_space;
881                for item in items {
882                    positions.push(current_pos);
883                    current_pos += item.main_size + gap_size;
884                }
885            }
886            JustifyContent::Center => {
887                let mut current_pos = remaining_space / 2.0;
888                for item in items {
889                    positions.push(current_pos);
890                    current_pos += item.main_size + gap_size;
891                }
892            }
893            JustifyContent::SpaceBetween => {
894                if items.len() == 1 {
895                    positions.push(0.0);
896                } else {
897                    let space_between = remaining_space / (items.len() as f32 - 1.0);
898                    let mut current_pos = 0.0;
899                    for item in items {
900                        positions.push(current_pos);
901                        current_pos += item.main_size + space_between;
902                    }
903                }
904            }
905            JustifyContent::SpaceAround => {
906                let space_around = remaining_space / items.len() as f32;
907                let mut current_pos = space_around / 2.0;
908                for item in items {
909                    positions.push(current_pos);
910                    current_pos += item.main_size + space_around;
911                }
912            }
913            JustifyContent::SpaceEvenly => {
914                let space_evenly = remaining_space / (items.len() as f32 + 1.0);
915                let mut current_pos = space_evenly;
916                for item in items {
917                    positions.push(current_pos);
918                    current_pos += item.main_size + space_evenly;
919                }
920            }
921        }
922        if is_reverse {
923            // Reverse the positions for reverse flex directions
924            let positions_len = positions.len();
925            for (i, pos) in positions.iter_mut().enumerate() {
926                let item_index = positions_len - 1 - i;
927                *pos = container_size - *pos - items[item_index].main_size;
928            }
929            positions.reverse();
930        }
931
932        positions
933    }
934
935    /// Calculate cross-axis position for an item
936    fn calculate_cross_axis_position(
937        &self,
938        child: &LayoutNode,
939        item_cross_size: f32,
940        container_cross_size: f32,
941        align_items: AlignItems,
942    ) -> f32 {
943        // Check for align-self override
944        let alignment = child.style.align_self.unwrap_or(align_items);
945
946        match alignment {
947            AlignItems::FlexStart => 0.0,
948            AlignItems::FlexEnd => container_cross_size - item_cross_size,
949            AlignItems::Center => (container_cross_size - item_cross_size) / 2.0,
950            AlignItems::Stretch => 0.0, // Item should already be sized to fill
951            AlignItems::Baseline => {
952                // For now, treat baseline as flex-start
953                // TODO: Implement proper baseline alignment
954                0.0
955            }
956        }
957    }
958
959    /// Layout children with flex wrap (multi-line)
960    fn layout_flex_multiline(
961        &mut self,
962        children: &mut [LayoutNode],
963        child_indices: &[usize],
964        container_size: Size,
965        parent_style: &LayoutStyle,
966    ) -> Result<(), LayoutError> {
967        let flex_direction = parent_style.flex_direction;
968        let is_row = matches!(
969            flex_direction,
970            FlexDirection::Row | FlexDirection::RowReverse
971        );
972        let is_reverse = matches!(parent_style.flex_wrap, FlexWrap::WrapReverse);
973
974        // Get the main axis size
975        let main_axis_size = if is_row {
976            container_size.width
977        } else {
978            container_size.height
979        }; // Break items into lines
980        let lines = self.break_into_lines(children, child_indices, main_axis_size, is_row)?;
981
982        if lines.is_empty() {
983            return Ok(());
984        }
985
986        // Layout each line
987        let cross_gap = if is_row {
988            parent_style.gap.row
989        } else {
990            parent_style.gap.column
991        };
992
993        // First calculate all line cross sizes so we have proper measurements
994        let mut line_cross_sizes = Vec::with_capacity(lines.len());
995
996        for line_indices in &lines {
997            if line_indices.is_empty() {
998                line_cross_sizes.push(0.0);
999                continue;
1000            }
1001
1002            // Calculate the cross size for this line
1003            let line_cross_size = self.calculate_line_cross_size(children, line_indices, is_row);
1004            line_cross_sizes.push(line_cross_size);
1005        }
1006
1007        // Now layout each line with the proper measurements
1008        let mut current_cross_pos = 0.0;
1009        for (i, line_indices) in lines.iter().enumerate() {
1010            if line_indices.is_empty() {
1011                continue;
1012            }
1013
1014            // Create a container size specific to this line
1015            let line_container = if is_row {
1016                Size::new(main_axis_size, container_size.height)
1017            } else {
1018                Size::new(container_size.width, main_axis_size)
1019            };
1020
1021            // Layout this line
1022            self.layout_flex_line(children, line_indices, line_container, parent_style)?;
1023            // First, recursively layout all children in this line
1024            for &child_index in line_indices {
1025                let child = &mut children[child_index];
1026                self.layout_node(child, child.layout.rect.size)?;
1027            }
1028
1029            // Then set the cross axis position for all items in this line
1030            for &child_index in line_indices {
1031                let child = &mut children[child_index];
1032                if is_row {
1033                    // Row layout - update Y position (cross axis)
1034                    child.layout.rect.origin.y = current_cross_pos;
1035                } else {
1036                    // Column layout - update X position (cross axis)
1037                    child.layout.rect.origin.x = current_cross_pos;
1038                }
1039            }
1040
1041            // Move to the next line
1042            current_cross_pos += line_cross_sizes[i] + cross_gap;
1043        }
1044
1045        // Handle wrap-reverse if needed
1046        if is_reverse {
1047            let total_cross_size = current_cross_pos - cross_gap;
1048            for &child_index in child_indices {
1049                let child = &mut children[child_index];
1050                if is_row {
1051                    child.layout.rect.origin.y =
1052                        total_cross_size - child.layout.rect.origin.y - child.layout.rect.height();
1053                } else {
1054                    child.layout.rect.origin.x =
1055                        total_cross_size - child.layout.rect.origin.x - child.layout.rect.width();
1056                }
1057            }
1058        }
1059
1060        Ok(())
1061    }
1062
1063    /// Break items into lines for wrapping
1064    fn break_into_lines(
1065        &self,
1066        children: &[LayoutNode],
1067        child_indices: &[usize],
1068        main_axis_size: f32,
1069        is_row: bool,
1070    ) -> Result<Vec<Vec<usize>>, LayoutError> {
1071        // Simple but effective algorithm for breaking items into lines
1072        let mut lines = Vec::new();
1073        let mut current_line = Vec::new();
1074        let mut current_line_size = 0.0;
1075
1076        // Get the appropriate gap size
1077        let gap_size = if is_row {
1078            children.first().map_or(0.0, |child| child.style.gap.column)
1079        } else {
1080            children.first().map_or(0.0, |child| child.style.gap.row)
1081        };
1082
1083        for &child_index in child_indices {
1084            let child = &children[child_index];
1085            let item_size = if is_row {
1086                child.style.width.resolve(main_axis_size)
1087            } else {
1088                child.style.height.resolve(main_axis_size)
1089            };
1090
1091            // Calculate the additional size needed if this isn't the first item (for gap)
1092            let gap_to_add = if current_line.is_empty() {
1093                0.0
1094            } else {
1095                gap_size
1096            };
1097
1098            // If this is the first item in a line OR it fits on the current line (including gap)
1099            if current_line.is_empty()
1100                || current_line_size + gap_to_add + item_size <= main_axis_size
1101            {
1102                current_line.push(child_index);
1103                current_line_size += item_size + gap_to_add;
1104            } else {
1105                // Start a new line
1106                lines.push(current_line);
1107                current_line = vec![child_index]; // Create a new Vec instead of clearing
1108                current_line_size = item_size;
1109            }
1110        }
1111
1112        // Add the last line if it's not empty
1113        if !current_line.is_empty() {
1114            lines.push(current_line);
1115        }
1116
1117        Ok(lines)
1118    }
1119    /// Calculate the cross-axis size of a line
1120    fn calculate_line_cross_size(
1121        &self,
1122        children: &[LayoutNode],
1123        line_indices: &[usize],
1124        is_row: bool,
1125    ) -> f32 {
1126        if line_indices.is_empty() {
1127            return 0.0;
1128        }
1129
1130        line_indices
1131            .iter()
1132            .map(|&index| {
1133                let child = &children[index];
1134                if is_row {
1135                    // For row layout, the cross size is the height
1136                    match child.style.height {
1137                        Dimension::Points(h) => h,
1138                        Dimension::Percent(p) => p * child.layout.rect.height(),
1139                        Dimension::Auto => child.layout.rect.height(),
1140                    }
1141                } else {
1142                    // For column layout, the cross size is the width
1143                    match child.style.width {
1144                        Dimension::Points(w) => w,
1145                        Dimension::Percent(p) => p * child.layout.rect.width(),
1146                        Dimension::Auto => child.layout.rect.width(),
1147                    }
1148                }
1149            })
1150            .fold(0.0, f32::max)
1151    }
1152}
1153
1154/// Data for a flex item during layout calculation
1155#[derive(Debug, Clone)]
1156struct FlexItemData {
1157    #[allow(dead_code)]
1158    index: usize,
1159    #[allow(dead_code)]
1160    basis_size: f32,
1161    main_size: f32,
1162    cross_size: f32,
1163    flex_grow: f32,
1164    flex_shrink: f32,
1165}
1166
1167/// Gap specification for flex containers
1168#[derive(Debug, Clone, Copy, PartialEq, Default)]
1169pub struct Gap {
1170    pub row: f32,
1171    pub column: f32,
1172}
1173
1174impl Gap {
1175    pub fn new(row: f32, column: f32) -> Self {
1176        Self { row, column }
1177    }
1178
1179    pub fn uniform(value: f32) -> Self {
1180        Self::new(value, value)
1181    }
1182}
1183
1184impl Default for LayoutEngine {
1185    fn default() -> Self {
1186        Self::new()
1187    }
1188}
1189
1190/// Errors that can occur during layout calculation
1191#[derive(Debug, thiserror::Error)]
1192pub enum LayoutError {
1193    #[error("Invalid layout constraint: {0}")]
1194    InvalidConstraint(String),
1195
1196    #[error("Layout calculation failed: {0}")]
1197    CalculationFailed(String),
1198
1199    #[error("Circular dependency detected in layout tree")]
1200    CircularDependency,
1201
1202    #[error("Node not found: {0:?}")]
1203    NodeNotFound(ComponentId),
1204}
1205
1206impl fmt::Display for LayoutStats {
1207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1208        write!(
1209            f,
1210            "Layout Stats: {} calculations, {}μs total time, {} nodes, {}/{} cache hit/miss",
1211            self.layout_calculations,
1212            self.layout_time_us,
1213            self.node_count,
1214            self.cache_hits,
1215            self.cache_misses
1216        )
1217    }
1218}
1219
1220#[cfg(test)]
1221mod tests {
1222    use super::*;
1223
1224    #[test]
1225    fn test_point_creation() {
1226        let point = Point::new(10.0, 20.0);
1227        assert_eq!(point.x, 10.0);
1228        assert_eq!(point.y, 20.0);
1229
1230        let zero_point = Point::zero();
1231        assert_eq!(zero_point.x, 0.0);
1232        assert_eq!(zero_point.y, 0.0);
1233    }
1234
1235    #[test]
1236    fn test_size_operations() {
1237        let size = Size::new(100.0, 200.0);
1238        assert_eq!(size.width, 100.0);
1239        assert_eq!(size.height, 200.0);
1240        assert_eq!(size.area(), 20000.0);
1241
1242        let zero_size = Size::zero();
1243        assert_eq!(zero_size.area(), 0.0);
1244    }
1245
1246    #[test]
1247    fn test_rect_operations() {
1248        let rect = Rect::new(10.0, 20.0, 100.0, 200.0);
1249        assert_eq!(rect.x(), 10.0);
1250        assert_eq!(rect.y(), 20.0);
1251        assert_eq!(rect.width(), 100.0);
1252        assert_eq!(rect.height(), 200.0);
1253        assert_eq!(rect.max_x(), 110.0);
1254        assert_eq!(rect.max_y(), 220.0);
1255
1256        let point_inside = Point::new(50.0, 100.0);
1257        let point_outside = Point::new(150.0, 100.0);
1258        assert!(rect.contains_point(point_inside));
1259        assert!(!rect.contains_point(point_outside));
1260    }
1261
1262    #[test]
1263    fn test_dimension_resolve() {
1264        let auto_dim = Dimension::Auto;
1265        let points_dim = Dimension::Points(100.0);
1266        let percent_dim = Dimension::Percent(50.0);
1267
1268        assert_eq!(auto_dim.resolve(200.0), 0.0);
1269        assert_eq!(points_dim.resolve(200.0), 100.0);
1270        assert_eq!(percent_dim.resolve(200.0), 100.0);
1271    }
1272
1273    #[test]
1274    fn test_edge_values() {
1275        let edges = EdgeValues::new(10.0, 20.0, 30.0, 40.0);
1276        assert_eq!(edges.horizontal(), 60.0);
1277        assert_eq!(edges.vertical(), 40.0);
1278
1279        let uniform = EdgeValues::uniform(15.0);
1280        assert_eq!(uniform.top, 15.0);
1281        assert_eq!(uniform.right, 15.0);
1282        assert_eq!(uniform.bottom, 15.0);
1283        assert_eq!(uniform.left, 15.0);
1284    }
1285
1286    #[test]
1287    fn test_layout_node_creation() {
1288        let id = ComponentId::new();
1289        let style = LayoutStyle::default();
1290        let node = LayoutNode::new(id, style);
1291
1292        assert_eq!(node.id, id);
1293        assert!(node.children.is_empty());
1294        assert!(node.layout.is_dirty);
1295    }
1296
1297    #[test]
1298    fn test_layout_node_children() {
1299        let parent_id = ComponentId::new();
1300        let child_id = ComponentId::new();
1301
1302        let mut parent = LayoutNode::new(parent_id, LayoutStyle::default());
1303        let child = LayoutNode::new(child_id, LayoutStyle::default());
1304
1305        parent.add_child(child);
1306        assert_eq!(parent.children.len(), 1);
1307        assert_eq!(parent.children[0].parent_id, Some(parent_id));
1308
1309        let removed_child = parent.remove_child(child_id);
1310        assert!(removed_child.is_some());
1311        assert_eq!(parent.children.len(), 0);
1312        assert_eq!(removed_child.unwrap().parent_id, None);
1313    }
1314
1315    #[test]
1316    fn test_layout_engine_creation() {
1317        let engine = LayoutEngine::new();
1318        assert_eq!(engine.stats.layout_calculations, 0);
1319        assert_eq!(engine.stats.cache_hits, 0);
1320        assert_eq!(engine.stats.cache_misses, 0);
1321    }
1322
1323    #[test]
1324    fn test_simple_layout_calculation() {
1325        let mut engine = LayoutEngine::new();
1326        let id = ComponentId::new();
1327        let style = LayoutStyle {
1328            width: Dimension::Points(100.0),
1329            height: Dimension::Points(200.0),
1330            ..Default::default()
1331        };
1332
1333        let mut root = LayoutNode::new(id, style);
1334        let container_size = Size::new(400.0, 600.0);
1335
1336        let result = engine.calculate_layout(&mut root, container_size);
1337        assert!(result.is_ok());
1338
1339        assert_eq!(root.layout.rect.width(), 100.0);
1340        assert_eq!(root.layout.rect.height(), 200.0);
1341        assert!(!root.layout.is_dirty);
1342    }
1343
1344    #[test]
1345    fn test_flexbox_row_layout() {
1346        let mut engine = LayoutEngine::new();
1347
1348        // Create parent with row flex direction
1349        let parent_id = ComponentId::new();
1350        let parent_style = LayoutStyle {
1351            flex_direction: FlexDirection::Row,
1352            width: Dimension::Points(300.0),
1353            height: Dimension::Points(100.0),
1354            ..Default::default()
1355        };
1356        let mut parent = LayoutNode::new(parent_id, parent_style);
1357
1358        // Add two children with flex grow
1359        let child1_id = ComponentId::new();
1360        let child1_style = LayoutStyle {
1361            flex_grow: 1.0,
1362            ..Default::default()
1363        };
1364        let child1 = LayoutNode::new(child1_id, child1_style);
1365
1366        let child2_id = ComponentId::new();
1367        let child2_style = LayoutStyle {
1368            flex_grow: 2.0,
1369            ..Default::default()
1370        };
1371        let child2 = LayoutNode::new(child2_id, child2_style);
1372
1373        parent.add_child(child1);
1374        parent.add_child(child2);
1375
1376        let container_size = Size::new(400.0, 200.0);
1377        let result = engine.calculate_layout(&mut parent, container_size);
1378        assert!(result.is_ok());
1379
1380        // Children should be laid out in a row
1381        assert_eq!(parent.children.len(), 2);
1382
1383        // Check that layout calculation was performed
1384        assert_eq!(engine.stats.layout_calculations, 1);
1385    }
1386
1387    #[test]
1388    fn test_layout_caching() {
1389        let mut engine = LayoutEngine::new();
1390        let id = ComponentId::new();
1391        let style = LayoutStyle::default();
1392        let mut root = LayoutNode::new(id, style);
1393        let container_size = Size::new(100.0, 100.0);
1394
1395        // First layout calculation
1396        let result = engine.calculate_layout(&mut root, container_size);
1397        assert!(result.is_ok());
1398        assert_eq!(engine.stats.cache_misses, 1);
1399
1400        // Mark as not dirty and recalculate - should use cache
1401        root.layout.is_dirty = false;
1402        let result = engine.calculate_layout(&mut root, container_size);
1403        assert!(result.is_ok());
1404
1405        // Should have used cache for the root node
1406        assert!(engine.stats.cache_hits > 0 || engine.stats.cache_misses > 1);
1407    }
1408
1409    #[test]
1410    fn test_layout_stats_display() {
1411        let stats = LayoutStats {
1412            layout_calculations: 5,
1413            layout_time_us: 1000,
1414            node_count: 10,
1415            cache_hits: 3,
1416            cache_misses: 7,
1417        };
1418        let display_string = format!("{stats}");
1419        assert!(display_string.contains("5 calculations"));
1420        assert!(display_string.contains("1000μs"));
1421        assert!(display_string.contains("10 nodes"));
1422        assert!(display_string.contains("3/7 cache"));
1423    }
1424    #[test]
1425    fn test_main_cross_axis_calculations() {
1426        let id = ComponentId::new();
1427
1428        // Test row direction
1429        let style = LayoutStyle {
1430            flex_direction: FlexDirection::Row,
1431            ..Default::default()
1432        };
1433        let mut node = LayoutNode::new(id, style);
1434        node.layout.rect = Rect::new(0.0, 0.0, 100.0, 50.0);
1435
1436        assert_eq!(node.main_axis_size(), 100.0); // width for row
1437        assert_eq!(node.cross_axis_size(), 50.0); // height for row
1438
1439        // Test column direction
1440        node.style.flex_direction = FlexDirection::Column;
1441        assert_eq!(node.main_axis_size(), 50.0); // height for column
1442        assert_eq!(node.cross_axis_size(), 100.0); // width for column
1443    }
1444
1445    #[test]
1446    fn test_justify_content_flex_start() {
1447        let mut engine = LayoutEngine::new();
1448        let parent_id = ComponentId::new();
1449        let parent_style = LayoutStyle {
1450            flex_direction: FlexDirection::Row,
1451            justify_content: JustifyContent::FlexStart,
1452            width: Dimension::Points(300.0),
1453            height: Dimension::Points(100.0),
1454            ..Default::default()
1455        };
1456        let mut parent = LayoutNode::new(parent_id, parent_style); // Add children with fixed sizes
1457        for _ in 0..3 {
1458            let child_id = ComponentId::new();
1459            let child_style = LayoutStyle {
1460                width: Dimension::Points(50.0),
1461                height: Dimension::Points(50.0),
1462                ..Default::default()
1463            };
1464            let child = LayoutNode::new(child_id, child_style);
1465            parent.add_child(child);
1466        }
1467
1468        let container_size = Size::new(400.0, 200.0);
1469        let result = engine.calculate_layout(&mut parent, container_size);
1470        assert!(result.is_ok());
1471
1472        // Children should be positioned at the start
1473        assert_eq!(parent.children[0].layout.rect.x(), 0.0);
1474        assert_eq!(parent.children[1].layout.rect.x(), 50.0);
1475        assert_eq!(parent.children[2].layout.rect.x(), 100.0);
1476    }
1477
1478    #[test]
1479    fn test_justify_content_center() {
1480        let mut engine = LayoutEngine::new();
1481        let parent_id = ComponentId::new();
1482        let parent_style = LayoutStyle {
1483            flex_direction: FlexDirection::Row,
1484            justify_content: JustifyContent::Center,
1485            width: Dimension::Points(300.0),
1486            height: Dimension::Points(100.0),
1487            ..Default::default()
1488        };
1489        let mut parent = LayoutNode::new(parent_id, parent_style);
1490
1491        // Add child with fixed size
1492        let child_id = ComponentId::new();
1493        let child_style = LayoutStyle {
1494            width: Dimension::Points(100.0),
1495            height: Dimension::Points(50.0),
1496            ..Default::default()
1497        };
1498        let child = LayoutNode::new(child_id, child_style);
1499        parent.add_child(child);
1500
1501        let container_size = Size::new(400.0, 200.0);
1502        let result = engine.calculate_layout(&mut parent, container_size);
1503        assert!(result.is_ok());
1504
1505        // Child should be centered: (300 - 100) / 2 = 100
1506        assert_eq!(parent.children[0].layout.rect.x(), 100.0);
1507    }
1508
1509    #[test]
1510    fn test_justify_content_space_between() {
1511        let mut engine = LayoutEngine::new();
1512        let parent_id = ComponentId::new();
1513        let parent_style = LayoutStyle {
1514            flex_direction: FlexDirection::Row,
1515            justify_content: JustifyContent::SpaceBetween,
1516            width: Dimension::Points(300.0),
1517            height: Dimension::Points(100.0),
1518            ..Default::default()
1519        };
1520        let mut parent = LayoutNode::new(parent_id, parent_style);
1521
1522        // Add two children with fixed sizes
1523        for _ in 0..2 {
1524            let child_id = ComponentId::new();
1525            let child_style = LayoutStyle {
1526                width: Dimension::Points(50.0),
1527                height: Dimension::Points(50.0),
1528                ..Default::default()
1529            };
1530            let child = LayoutNode::new(child_id, child_style);
1531            parent.add_child(child);
1532        }
1533
1534        let container_size = Size::new(400.0, 200.0);
1535        let result = engine.calculate_layout(&mut parent, container_size);
1536        assert!(result.is_ok());
1537
1538        // First child at start, second at end
1539        assert_eq!(parent.children[0].layout.rect.x(), 0.0);
1540        assert_eq!(parent.children[1].layout.rect.x(), 250.0); // 300 - 50
1541    }
1542
1543    #[test]
1544    fn test_align_items_center() {
1545        let mut engine = LayoutEngine::new();
1546        let parent_id = ComponentId::new();
1547        let parent_style = LayoutStyle {
1548            flex_direction: FlexDirection::Row,
1549            align_items: AlignItems::Center,
1550            width: Dimension::Points(300.0),
1551            height: Dimension::Points(100.0),
1552            ..Default::default()
1553        };
1554        let mut parent = LayoutNode::new(parent_id, parent_style);
1555
1556        // Add child with fixed size
1557        let child_id = ComponentId::new();
1558        let child_style = LayoutStyle {
1559            width: Dimension::Points(50.0),
1560            height: Dimension::Points(30.0),
1561            ..Default::default()
1562        };
1563        let child = LayoutNode::new(child_id, child_style);
1564        parent.add_child(child);
1565
1566        let container_size = Size::new(400.0, 200.0);
1567        let result = engine.calculate_layout(&mut parent, container_size);
1568        assert!(result.is_ok());
1569
1570        // Child should be vertically centered: (100 - 30) / 2 = 35
1571        assert_eq!(parent.children[0].layout.rect.y(), 35.0);
1572    }
1573
1574    #[test]
1575    fn test_align_items_flex_end() {
1576        let mut engine = LayoutEngine::new();
1577        let parent_id = ComponentId::new();
1578        let parent_style = LayoutStyle {
1579            flex_direction: FlexDirection::Row,
1580            align_items: AlignItems::FlexEnd,
1581            width: Dimension::Points(300.0),
1582            height: Dimension::Points(100.0),
1583            ..Default::default()
1584        };
1585        let mut parent = LayoutNode::new(parent_id, parent_style);
1586
1587        // Add child with fixed size
1588        let child_id = ComponentId::new();
1589        let child_style = LayoutStyle {
1590            width: Dimension::Points(50.0),
1591            height: Dimension::Points(30.0),
1592            ..Default::default()
1593        };
1594        let child = LayoutNode::new(child_id, child_style);
1595        parent.add_child(child);
1596
1597        let container_size = Size::new(400.0, 200.0);
1598        let result = engine.calculate_layout(&mut parent, container_size);
1599        assert!(result.is_ok());
1600
1601        // Child should be at bottom: 100 - 30 = 70
1602        assert_eq!(parent.children[0].layout.rect.y(), 70.0);
1603    }
1604
1605    #[test]
1606    fn test_flex_grow_distribution() {
1607        let mut engine = LayoutEngine::new();
1608        let parent_id = ComponentId::new();
1609        let parent_style = LayoutStyle {
1610            flex_direction: FlexDirection::Row,
1611            width: Dimension::Points(300.0),
1612            height: Dimension::Points(100.0),
1613            ..Default::default()
1614        };
1615        let mut parent = LayoutNode::new(parent_id, parent_style);
1616
1617        // Add children with different flex grow values
1618        let child1_style = LayoutStyle {
1619            flex_grow: 1.0,
1620            flex_basis: Dimension::Points(50.0),
1621            ..Default::default()
1622        };
1623        let child1 = LayoutNode::new(ComponentId::new(), child1_style);
1624        parent.add_child(child1);
1625
1626        let child2_style = LayoutStyle {
1627            flex_grow: 2.0,
1628            flex_basis: Dimension::Points(50.0),
1629            ..Default::default()
1630        };
1631        let child2 = LayoutNode::new(ComponentId::new(), child2_style);
1632        parent.add_child(child2);
1633
1634        let container_size = Size::new(400.0, 200.0);
1635        let result = engine.calculate_layout(&mut parent, container_size);
1636        assert!(result.is_ok());
1637
1638        // Available space: 300 - 100 = 200
1639        // Child1 gets 1/3 * 200 = 66.67, Child2 gets 2/3 * 200 = 133.33
1640        // Final sizes: Child1 = 50 + 66.67 = 116.67, Child2 = 50 + 133.33 = 183.33
1641        assert!((parent.children[0].layout.rect.width() - 116.67).abs() < 0.1);
1642        assert!((parent.children[1].layout.rect.width() - 183.33).abs() < 0.1);
1643    }
1644
1645    #[test]
1646    fn test_gap_spacing() {
1647        let mut engine = LayoutEngine::new();
1648        let parent_id = ComponentId::new();
1649        let parent_style = LayoutStyle {
1650            flex_direction: FlexDirection::Row,
1651            width: Dimension::Points(300.0),
1652            height: Dimension::Points(100.0),
1653            gap: Gap::uniform(10.0),
1654            ..Default::default()
1655        };
1656        let mut parent = LayoutNode::new(parent_id, parent_style);
1657
1658        // Add two children
1659        for _ in 0..2 {
1660            let child_style = LayoutStyle {
1661                width: Dimension::Points(50.0),
1662                height: Dimension::Points(50.0),
1663                ..Default::default()
1664            };
1665            let child = LayoutNode::new(ComponentId::new(), child_style);
1666            parent.add_child(child);
1667        }
1668
1669        let container_size = Size::new(400.0, 200.0);
1670        let result = engine.calculate_layout(&mut parent, container_size);
1671        assert!(result.is_ok());
1672
1673        // Second child should be positioned after first child + gap
1674        assert_eq!(parent.children[0].layout.rect.x(), 0.0);
1675        assert_eq!(parent.children[1].layout.rect.x(), 60.0); // 50 + 10 gap
1676    }
1677
1678    #[test]
1679    fn test_column_direction() {
1680        let mut engine = LayoutEngine::new();
1681        let parent_id = ComponentId::new();
1682        let parent_style = LayoutStyle {
1683            flex_direction: FlexDirection::Column,
1684            width: Dimension::Points(100.0),
1685            height: Dimension::Points(300.0),
1686            ..Default::default()
1687        };
1688        let mut parent = LayoutNode::new(parent_id, parent_style);
1689
1690        // Add children with flex grow
1691        for i in 0..2 {
1692            let child_style = LayoutStyle {
1693                flex_grow: (i + 1) as f32,
1694                ..Default::default()
1695            };
1696            let child = LayoutNode::new(ComponentId::new(), child_style);
1697            parent.add_child(child);
1698        }
1699
1700        let container_size = Size::new(200.0, 400.0);
1701        let result = engine.calculate_layout(&mut parent, container_size);
1702        assert!(result.is_ok());
1703
1704        // Children should be stacked vertically
1705        assert_eq!(parent.children[0].layout.rect.y(), 0.0);
1706        assert!(parent.children[1].layout.rect.y() > parent.children[0].layout.rect.height());
1707    }
1708
1709    #[test]
1710    fn test_flex_wrap_basic() {
1711        let mut engine = LayoutEngine::new();
1712        let parent_id = ComponentId::new();
1713        let parent_style = LayoutStyle {
1714            flex_direction: FlexDirection::Row,
1715            flex_wrap: FlexWrap::Wrap,
1716            width: Dimension::Points(200.0),
1717            height: Dimension::Points(200.0),
1718            ..Default::default()
1719        };
1720        let mut parent = LayoutNode::new(parent_id, parent_style);
1721
1722        // Add children that will overflow and wrap
1723        for _ in 0..3 {
1724            let child_style = LayoutStyle {
1725                width: Dimension::Points(100.0),
1726                height: Dimension::Points(50.0),
1727                ..Default::default()
1728            };
1729            let child = LayoutNode::new(ComponentId::new(), child_style);
1730            parent.add_child(child);
1731        }
1732        let container_size = Size::new(300.0, 400.0);
1733        let result = engine.calculate_layout(&mut parent, container_size);
1734        assert!(result.is_ok());
1735
1736        // First two children should be on first line, third on second line
1737        assert_eq!(
1738            parent.children[0].layout.rect.y(),
1739            parent.children[1].layout.rect.y()
1740        );
1741        assert!(parent.children[2].layout.rect.y() > parent.children[0].layout.rect.y());
1742    }
1743
1744    #[test]
1745    fn test_gap_structure() {
1746        let gap = Gap::new(10.0, 20.0);
1747        assert_eq!(gap.row, 10.0);
1748        assert_eq!(gap.column, 20.0);
1749
1750        let uniform_gap = Gap::uniform(15.0);
1751        assert_eq!(uniform_gap.row, 15.0);
1752        assert_eq!(uniform_gap.column, 15.0);
1753
1754        let default_gap = Gap::default();
1755        assert_eq!(default_gap.row, 0.0);
1756        assert_eq!(default_gap.column, 0.0);
1757    }
1758}