1use rosace_core::child_container::ChildContainer;
7use rosace_core::element::{Element, NativeElement};
8#[cfg(debug_assertions)]
9use rosace_core::render_object::AxisBound;
10use rosace_core::types::{Point, Size};
11#[cfg(debug_assertions)]
12use rosace_trace::{
13 event::{ComponentId, RosaceTrace, TraceConstraints},
14 trace,
15};
16
17use crate::alignment::{CrossAxisAlignment, MainAxisAlignment};
18use crate::constraints::Constraints;
19use crate::layout_result::LayoutResult;
20
21#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum FlexDirection {
24 Row,
26 Column,
28}
29
30#[derive(Debug, Clone)]
38pub struct Flex {
39 pub direction: FlexDirection,
41 pub children: Vec<Element>,
43 pub main_axis_alignment: MainAxisAlignment,
45 pub cross_axis_alignment: CrossAxisAlignment,
47 pub spacing: f32,
49}
50
51impl Flex {
52 pub fn new(direction: FlexDirection) -> Self {
54 Self {
55 direction,
56 children: Vec::new(),
57 main_axis_alignment: MainAxisAlignment::default(),
58 cross_axis_alignment: CrossAxisAlignment::default(),
59 spacing: 0.0,
60 }
61 }
62
63 pub fn spacing(mut self, s: f32) -> Self {
65 self.spacing = s;
66 self
67 }
68
69 pub fn main_axis_alignment(mut self, a: MainAxisAlignment) -> Self {
71 self.main_axis_alignment = a;
72 self
73 }
74
75 pub fn cross_axis_alignment(mut self, a: CrossAxisAlignment) -> Self {
77 self.cross_axis_alignment = a;
78 self
79 }
80
81 pub fn layout(&self, constraints: Constraints, child_sizes: &[Size]) -> LayoutResult {
86 #[cfg(debug_assertions)]
87 let start = std::time::Instant::now();
88
89 #[cfg(debug_assertions)]
90 trace!(RosaceTrace::LayoutStart {
91 component: ComponentId(0),
92 constraints: TraceConstraints {
93 min_width: constraints.min_width,
94 max_width: match &constraints.max_width {
95 AxisBound::Bounded(v) => Some(*v),
96 _ => None,
97 },
98 min_height: constraints.min_height,
99 max_height: match &constraints.max_height {
100 AxisBound::Bounded(v) => Some(*v),
101 _ => None,
102 },
103 },
104 });
105
106 let result = self.layout_inner(constraints, child_sizes);
107
108 #[cfg(debug_assertions)]
109 trace!(RosaceTrace::LayoutEnd {
110 component: ComponentId(0),
111 size: result.size,
112 duration: start.elapsed(),
113 });
114
115 result
116 }
117
118 pub(crate) fn layout_inner(
124 &self,
125 constraints: Constraints,
126 child_sizes: &[Size],
127 ) -> LayoutResult {
128 match self.direction {
129 FlexDirection::Column => layout_column(
130 constraints,
131 child_sizes,
132 self.main_axis_alignment,
133 self.cross_axis_alignment,
134 self.spacing,
135 ),
136 FlexDirection::Row => layout_row(
137 constraints,
138 child_sizes,
139 self.main_axis_alignment,
140 self.cross_axis_alignment,
141 self.spacing,
142 ),
143 }
144 }
145}
146
147pub fn layout_column(
149 constraints: Constraints,
150 child_sizes: &[Size],
151 main_axis_alignment: MainAxisAlignment,
152 cross_axis_alignment: CrossAxisAlignment,
153 spacing: f32,
154) -> LayoutResult {
155 let n = child_sizes.len();
156 if n == 0 {
157 return LayoutResult {
158 size: constraints.constrain(Size {
159 width: 0.0,
160 height: 0.0,
161 }),
162 child_positions: vec![],
163 };
164 }
165
166 let max_w = constraints.max_width_f32();
167 let max_h = constraints.max_height_f32();
168
169 let content_width = child_sizes
171 .iter()
172 .map(|s| s.width)
173 .fold(0.0_f32, f32::max);
174 let container_width = match cross_axis_alignment {
175 CrossAxisAlignment::Stretch if max_w.is_finite() => max_w,
176 _ => content_width,
177 }
178 .max(constraints.min_width)
179 .min(max_w);
180
181 let total_child_height: f32 = child_sizes.iter().map(|s| s.height).sum();
183 let total_spacing = spacing * (n - 1) as f32;
184 let content_height = total_child_height + total_spacing;
185 let container_height = content_height
186 .max(constraints.min_height)
187 .min(max_h);
188
189 let extra = (container_height - content_height).max(0.0);
190 let (initial_offset, between_gap) = distribute_extra(main_axis_alignment, extra, n);
191
192 let mut positions = Vec::with_capacity(n);
193 let mut y = initial_offset;
194
195 for (i, child_size) in child_sizes.iter().enumerate() {
196 let x = cross_offset(cross_axis_alignment, container_width, child_size.width);
197 positions.push(Point { x, y });
198 y += child_size.height;
199 if i + 1 < n {
200 y += spacing + between_gap;
201 }
202 }
203
204 LayoutResult {
205 size: Size {
206 width: container_width,
207 height: container_height,
208 },
209 child_positions: positions,
210 }
211}
212
213pub fn layout_row(
215 constraints: Constraints,
216 child_sizes: &[Size],
217 main_axis_alignment: MainAxisAlignment,
218 cross_axis_alignment: CrossAxisAlignment,
219 spacing: f32,
220) -> LayoutResult {
221 let n = child_sizes.len();
222 if n == 0 {
223 return LayoutResult {
224 size: constraints.constrain(Size {
225 width: 0.0,
226 height: 0.0,
227 }),
228 child_positions: vec![],
229 };
230 }
231
232 let max_w = constraints.max_width_f32();
233 let max_h = constraints.max_height_f32();
234
235 let content_height = child_sizes
237 .iter()
238 .map(|s| s.height)
239 .fold(0.0_f32, f32::max);
240 let container_height = match cross_axis_alignment {
241 CrossAxisAlignment::Stretch if max_h.is_finite() => max_h,
242 _ => content_height,
243 }
244 .max(constraints.min_height)
245 .min(max_h);
246
247 let total_child_width: f32 = child_sizes.iter().map(|s| s.width).sum();
249 let total_spacing = spacing * (n - 1) as f32;
250 let content_width = total_child_width + total_spacing;
251 let container_width = content_width
252 .max(constraints.min_width)
253 .min(max_w);
254
255 let extra = (container_width - content_width).max(0.0);
256 let (initial_offset, between_gap) = distribute_extra(main_axis_alignment, extra, n);
257
258 let mut positions = Vec::with_capacity(n);
259 let mut x = initial_offset;
260
261 for (i, child_size) in child_sizes.iter().enumerate() {
262 let y = cross_offset(cross_axis_alignment, container_height, child_size.height);
263 positions.push(Point { x, y });
264 x += child_size.width;
265 if i + 1 < n {
266 x += spacing + between_gap;
267 }
268 }
269
270 LayoutResult {
271 size: Size {
272 width: container_width,
273 height: container_height,
274 },
275 child_positions: positions,
276 }
277}
278
279pub(crate) fn distribute_extra(
284 alignment: MainAxisAlignment,
285 extra: f32,
286 n: usize,
287) -> (f32, f32) {
288 match alignment {
289 MainAxisAlignment::Start => (0.0, 0.0),
290 MainAxisAlignment::Center => (extra / 2.0, 0.0),
291 MainAxisAlignment::End => (extra, 0.0),
292 MainAxisAlignment::SpaceBetween => {
293 let gap = if n > 1 {
294 extra / (n - 1) as f32
295 } else {
296 0.0
297 };
298 (0.0, gap)
299 }
300 MainAxisAlignment::SpaceAround => {
301 let unit = if n > 0 { extra / n as f32 } else { 0.0 };
302 (unit / 2.0, unit)
303 }
304 MainAxisAlignment::SpaceEvenly => {
305 let unit = extra / (n + 1) as f32;
306 (unit, unit)
307 }
308 }
309}
310
311pub(crate) fn cross_offset(alignment: CrossAxisAlignment, container: f32, child: f32) -> f32 {
313 match alignment {
314 CrossAxisAlignment::Start | CrossAxisAlignment::Stretch | CrossAxisAlignment::Baseline => {
315 0.0
316 }
317 CrossAxisAlignment::Center => (container - child) / 2.0,
318 CrossAxisAlignment::End => container - child,
319 }
320}
321
322impl ChildContainer for Flex {
323 fn child(mut self, element: impl Into<Element>) -> Self {
324 self.children.push(element.into());
325 self
326 }
327
328 fn children<E: Into<Element>>(mut self, elements: Vec<E>) -> Self {
329 self.children
330 .extend(elements.into_iter().map(Into::into));
331 self
332 }
333
334 fn prepend(mut self, element: impl Into<Element>) -> Self {
335 self.children.insert(0, element.into());
336 self
337 }
338}
339
340impl From<Flex> for Element {
341 fn from(f: Flex) -> Self {
342 Element::Native(NativeElement {
343 tag: "Flex",
344 payload: None,
345 children: f.children,
346 key: None,
347 })
348 }
349}