rosace_layout/widgets/
grid.rs1use rosace_core::child_container::ChildContainer;
4use rosace_core::element::{Element, NativeElement};
5#[cfg(debug_assertions)]
6use rosace_core::render_object::AxisBound;
7use rosace_core::types::{Point, Size};
8#[cfg(debug_assertions)]
9use rosace_trace::{
10 event::{ComponentId, RosaceTrace, TraceConstraints},
11 trace,
12};
13
14use crate::constraints::Constraints;
15use crate::layout_result::LayoutResult;
16
17#[derive(Debug, Clone)]
23pub struct Grid {
24 children: Vec<Element>,
25 columns: usize,
26 spacing: f32,
27}
28
29impl Grid {
30 pub fn new(columns: usize) -> Self {
32 Self {
33 children: Vec::new(),
34 columns: columns.max(1),
35 spacing: 0.0,
36 }
37 }
38
39 pub fn spacing(mut self, s: f32) -> Self {
41 self.spacing = s;
42 self
43 }
44
45 pub fn layout(&self, constraints: Constraints, child_sizes: &[Size]) -> LayoutResult {
52 #[cfg(debug_assertions)]
53 let start = std::time::Instant::now();
54
55 #[cfg(debug_assertions)]
56 trace!(RosaceTrace::LayoutStart {
57 component: ComponentId(0),
58 constraints: TraceConstraints {
59 min_width: constraints.min_width,
60 max_width: match &constraints.max_width {
61 AxisBound::Bounded(v) => Some(*v),
62 _ => None,
63 },
64 min_height: constraints.min_height,
65 max_height: match &constraints.max_height {
66 AxisBound::Bounded(v) => Some(*v),
67 _ => None,
68 },
69 },
70 });
71
72 let n = child_sizes.len();
73 let result = if n == 0 {
74 LayoutResult {
75 size: constraints.constrain(Size {
76 width: 0.0,
77 height: 0.0,
78 }),
79 child_positions: vec![],
80 }
81 } else {
82 let cols = self.columns;
83 let rows = n.div_ceil(cols);
84
85 let mut col_widths = vec![0.0_f32; cols];
87 for (i, size) in child_sizes.iter().enumerate() {
88 let col = i % cols;
89 col_widths[col] = col_widths[col].max(size.width);
90 }
91
92 let mut row_heights = vec![0.0_f32; rows];
94 for (i, size) in child_sizes.iter().enumerate() {
95 let row = i / cols;
96 row_heights[row] = row_heights[row].max(size.height);
97 }
98
99 let mut col_offsets = vec![0.0_f32; cols];
101 for c in 1..cols {
102 col_offsets[c] = col_offsets[c - 1] + col_widths[c - 1] + self.spacing;
103 }
104
105 let mut row_offsets = vec![0.0_f32; rows];
107 for r in 1..rows {
108 row_offsets[r] = row_offsets[r - 1] + row_heights[r - 1] + self.spacing;
109 }
110
111 let mut positions = Vec::with_capacity(n);
112 for i in 0..n {
113 let col = i % cols;
114 let row = i / cols;
115 positions.push(Point {
116 x: col_offsets[col],
117 y: row_offsets[row],
118 });
119 }
120
121 let total_w: f32 =
122 col_widths.iter().sum::<f32>() + self.spacing * (cols - 1) as f32;
123 let total_h: f32 =
124 row_heights.iter().sum::<f32>() + self.spacing * (rows - 1) as f32;
125
126 LayoutResult {
127 size: constraints.constrain(Size {
128 width: total_w,
129 height: total_h,
130 }),
131 child_positions: positions,
132 }
133 };
134
135 #[cfg(debug_assertions)]
136 trace!(RosaceTrace::LayoutEnd {
137 component: ComponentId(0),
138 size: result.size,
139 duration: start.elapsed(),
140 });
141
142 result
143 }
144}
145
146impl ChildContainer for Grid {
147 fn child(mut self, element: impl Into<Element>) -> Self {
148 self.children.push(element.into());
149 self
150 }
151
152 fn children<E: Into<Element>>(mut self, elements: Vec<E>) -> Self {
153 self.children
154 .extend(elements.into_iter().map(Into::into));
155 self
156 }
157
158 fn prepend(mut self, element: impl Into<Element>) -> Self {
159 self.children.insert(0, element.into());
160 self
161 }
162}
163
164impl From<Grid> for Element {
165 fn from(g: Grid) -> Self {
166 Element::Native(NativeElement {
167 tag: "Grid",
168 payload: None,
169 children: g.children,
170 key: None,
171 })
172 }
173}