Skip to main content

rosace_layout/
lib.rs

1//! `rosace-layout` — Phase 1 layout engine and widgets for ROSACE.
2//!
3//! This crate provides:
4//! - The [`Flexure`] constraint-based layout engine.
5//! - Constraint helpers re-exported from `rosace-core` ([`Constraints`], [`AxisBound`]).
6//! - Sizing enumerations ([`Width`], [`Height`]).
7//! - Alignment enumerations ([`MainAxisAlignment`], [`CrossAxisAlignment`]).
8//! - A full set of Phase 1 layout widgets (see [`widgets`]).
9
10pub mod alignment;
11pub mod constraints;
12pub mod flexure;
13pub mod layout_result;
14pub mod sizing;
15pub mod widgets;
16
17pub use alignment::{CrossAxisAlignment, MainAxisAlignment};
18pub use constraints::{AxisBound, Constraints};
19pub use flexure::Flexure;
20pub use layout_result::LayoutResult;
21pub use sizing::{Height, Width};
22pub use widgets::flex::{layout_column, layout_row};
23pub use widgets::{
24    aspect_ratio::AspectRatio,
25    flex::{Flex, FlexDirection},
26    grid::Grid,
27    wrap::Wrap,
28};
29
30#[cfg(test)]
31mod tests {
32    use rosace_core::types::Size;
33
34    use crate::{
35        alignment::{CrossAxisAlignment, MainAxisAlignment},
36        constraints::Constraints,
37        sizing::Width,
38        widgets::{flex::{layout_column, layout_row}, grid::Grid, wrap::Wrap},
39    };
40
41    #[test]
42    fn column_stacks_children_vertically() {
43        let child_sizes = vec![
44            Size { width: 100.0, height: 30.0 },
45            Size { width: 80.0, height: 40.0 },
46        ];
47        let result = layout_column(
48            Constraints::loose(400.0, 600.0), &child_sizes,
49            MainAxisAlignment::Start, CrossAxisAlignment::Start, 8.0,
50        );
51        // Second child should be placed below first + spacing.
52        assert_eq!(result.child_positions[1].y, 30.0 + 8.0);
53    }
54
55    #[test]
56    fn row_stacks_children_horizontally() {
57        let child_sizes = vec![
58            Size { width: 50.0, height: 20.0 },
59            Size { width: 60.0, height: 20.0 },
60        ];
61        let result = layout_row(
62            Constraints::loose(400.0, 100.0), &child_sizes,
63            MainAxisAlignment::Start, CrossAxisAlignment::Start, 4.0,
64        );
65        assert_eq!(result.child_positions[1].x, 50.0 + 4.0);
66    }
67
68    #[test]
69    fn constraints_constrain_clamps_correctly() {
70        let c = Constraints::loose(100.0, 100.0);
71        let big = Size { width: 200.0, height: 200.0 };
72        let clamped = c.constrain(big);
73        assert_eq!(clamped.width, 100.0);
74        assert_eq!(clamped.height, 100.0);
75    }
76
77    #[test]
78    fn constraints_loose_has_zero_min() {
79        let c = Constraints::loose(800.0, 600.0);
80        assert_eq!(c.min_width, 0.0);
81        assert_eq!(c.min_height, 0.0);
82    }
83
84    #[test]
85    fn fraction_width_is_of_available_space() {
86        let w = Width::Fraction(0.5);
87        let bound = w.to_axis_bound(200.0);
88        assert!(
89            matches!(bound, rosace_core::render_object::AxisBound::Bounded(v) if (v - 100.0).abs() < 0.001)
90        );
91    }
92
93    #[test]
94    fn grid_positions_children_in_rows() {
95        let grid = Grid::new(3).spacing(0.0);
96        let sizes = vec![Size { width: 50.0, height: 50.0 }; 6];
97        let result = grid.layout(Constraints::loose(200.0, 400.0), &sizes);
98        // 4th item (index 3) should be in the second row.
99        assert_eq!(result.child_positions[3].x, 0.0);
100        assert!(result.child_positions[3].y > 0.0);
101    }
102
103    #[test]
104    fn wrap_wraps_to_next_line_when_full() {
105        let wrap = Wrap::new().spacing(0.0);
106        let sizes = vec![Size { width: 60.0, height: 30.0 }; 4];
107        let result = wrap.layout(Constraints::loose(150.0, 400.0), &sizes);
108        // With width 150 and items 60 wide, only 2 fit per row; 3rd item wraps.
109        assert!(result.child_positions[2].y > 0.0);
110    }
111}