1use presentar_core::{
4 widget::LayoutResult, Canvas, Constraints, Event, Rect, Size, TypeId, Widget,
5};
6use serde::{Deserialize, Serialize};
7use std::any::Any;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
11pub enum MainAxisAlignment {
12 #[default]
14 Start,
15 End,
17 Center,
19 SpaceBetween,
21 SpaceAround,
23 SpaceEvenly,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
29pub enum CrossAxisAlignment {
30 Start,
32 End,
34 #[default]
36 Center,
37 Stretch,
39}
40
41#[derive(Serialize, Deserialize)]
43pub struct Row {
44 main_axis_alignment: MainAxisAlignment,
46 cross_axis_alignment: CrossAxisAlignment,
48 gap: f32,
50 #[serde(skip)]
52 children: Vec<Box<dyn Widget>>,
53 test_id_value: Option<String>,
55 #[serde(skip)]
57 bounds: Rect,
58 #[serde(skip)]
60 child_bounds: Vec<Rect>,
61}
62
63impl Default for Row {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69impl Row {
70 #[must_use]
72 pub fn new() -> Self {
73 Self {
74 main_axis_alignment: MainAxisAlignment::Start,
75 cross_axis_alignment: CrossAxisAlignment::Center,
76 gap: 0.0,
77 children: Vec::new(),
78 test_id_value: None,
79 bounds: Rect::default(),
80 child_bounds: Vec::new(),
81 }
82 }
83
84 #[must_use]
86 pub const fn main_axis_alignment(mut self, alignment: MainAxisAlignment) -> Self {
87 self.main_axis_alignment = alignment;
88 self
89 }
90
91 #[must_use]
93 pub const fn cross_axis_alignment(mut self, alignment: CrossAxisAlignment) -> Self {
94 self.cross_axis_alignment = alignment;
95 self
96 }
97
98 #[must_use]
100 pub const fn gap(mut self, gap: f32) -> Self {
101 self.gap = gap;
102 self
103 }
104
105 pub fn child(mut self, widget: impl Widget + 'static) -> Self {
107 self.children.push(Box::new(widget));
108 self
109 }
110
111 #[must_use]
113 pub fn with_test_id(mut self, id: impl Into<String>) -> Self {
114 self.test_id_value = Some(id.into());
115 self
116 }
117}
118
119impl Widget for Row {
120 fn type_id(&self) -> TypeId {
121 TypeId::of::<Self>()
122 }
123
124 fn measure(&self, constraints: Constraints) -> Size {
125 if self.children.is_empty() {
126 return Size::ZERO;
127 }
128
129 let mut total_width = 0.0f32;
130 let mut max_height = 0.0f32;
131
132 for (i, child) in self.children.iter().enumerate() {
134 let child_constraints = Constraints::new(
135 0.0,
136 (constraints.max_width - total_width).max(0.0),
137 0.0,
138 constraints.max_height,
139 );
140
141 let child_size = child.measure(child_constraints);
142 total_width += child_size.width;
143 max_height = max_height.max(child_size.height);
144
145 if i < self.children.len() - 1 {
146 total_width += self.gap;
147 }
148 }
149
150 constraints.constrain(Size::new(total_width, max_height))
151 }
152
153 fn layout(&mut self, bounds: Rect) -> LayoutResult {
154 self.bounds = bounds;
155 self.child_bounds.clear();
156
157 if self.children.is_empty() {
158 return LayoutResult { size: Size::ZERO };
159 }
160
161 let mut child_sizes: Vec<Size> = Vec::with_capacity(self.children.len());
163 let mut total_width = 0.0f32;
164
165 for child in &self.children {
166 let child_constraints = Constraints::loose(bounds.size());
167 let size = child.measure(child_constraints);
168 total_width += size.width;
169 child_sizes.push(size);
170 }
171
172 let gaps_width = self.gap * (self.children.len() - 1).max(0) as f32;
173 let content_width = total_width + gaps_width;
174 let remaining_space = (bounds.width - content_width).max(0.0);
175
176 let (mut x, extra_gap) = match self.main_axis_alignment {
178 MainAxisAlignment::Start => (bounds.x, 0.0),
179 MainAxisAlignment::End => (bounds.x + remaining_space, 0.0),
180 MainAxisAlignment::Center => (bounds.x + remaining_space / 2.0, 0.0),
181 MainAxisAlignment::SpaceBetween => {
182 if self.children.len() > 1 {
183 (bounds.x, remaining_space / (self.children.len() - 1) as f32)
184 } else {
185 (bounds.x, 0.0)
186 }
187 }
188 MainAxisAlignment::SpaceAround => {
189 let gap = remaining_space / self.children.len() as f32;
190 (bounds.x + gap / 2.0, gap)
191 }
192 MainAxisAlignment::SpaceEvenly => {
193 let gap = remaining_space / (self.children.len() + 1) as f32;
194 (bounds.x + gap, gap)
195 }
196 };
197
198 let num_children = self.children.len();
200 for (i, (child, size)) in self.children.iter_mut().zip(child_sizes.iter()).enumerate() {
201 let y = match self.cross_axis_alignment {
202 CrossAxisAlignment::Start | CrossAxisAlignment::Stretch => bounds.y,
203 CrossAxisAlignment::End => bounds.y + bounds.height - size.height,
204 CrossAxisAlignment::Center => bounds.y + (bounds.height - size.height) / 2.0,
205 };
206
207 let height = if self.cross_axis_alignment == CrossAxisAlignment::Stretch {
208 bounds.height
209 } else {
210 size.height
211 };
212
213 let child_bounds = Rect::new(x, y, size.width, height);
214 child.layout(child_bounds);
215 self.child_bounds.push(child_bounds);
216
217 if i < num_children - 1 {
219 x += size.width;
220 if self.main_axis_alignment == MainAxisAlignment::SpaceBetween {
221 x += extra_gap;
223 } else {
224 x += self.gap + extra_gap;
225 }
226 }
227 }
228
229 LayoutResult {
230 size: bounds.size(),
231 }
232 }
233
234 fn paint(&self, canvas: &mut dyn Canvas) {
235 for child in &self.children {
236 child.paint(canvas);
237 }
238 }
239
240 fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>> {
241 for child in &mut self.children {
242 if let Some(msg) = child.event(event) {
243 return Some(msg);
244 }
245 }
246 None
247 }
248
249 fn children(&self) -> &[Box<dyn Widget>] {
250 &self.children
251 }
252
253 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
254 &mut self.children
255 }
256
257 fn test_id(&self) -> Option<&str> {
258 self.test_id_value.as_deref()
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use presentar_core::widget::AccessibleRole;
266 use presentar_core::Widget;
267
268 struct FixedWidget {
270 size: Size,
271 }
272
273 impl FixedWidget {
274 fn new(width: f32, height: f32) -> Self {
275 Self {
276 size: Size::new(width, height),
277 }
278 }
279 }
280
281 impl Widget for FixedWidget {
282 fn type_id(&self) -> TypeId {
283 TypeId::of::<Self>()
284 }
285
286 fn measure(&self, constraints: Constraints) -> Size {
287 constraints.constrain(self.size)
288 }
289
290 fn layout(&mut self, _bounds: Rect) -> LayoutResult {
291 LayoutResult { size: self.size }
292 }
293
294 fn paint(&self, _canvas: &mut dyn Canvas) {}
295
296 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
297 None
298 }
299
300 fn children(&self) -> &[Box<dyn Widget>] {
301 &[]
302 }
303
304 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
305 &mut []
306 }
307
308 fn accessible_role(&self) -> AccessibleRole {
309 AccessibleRole::Generic
310 }
311 }
312
313 #[test]
316 fn test_row_empty() {
317 let row = Row::new();
318 let size = row.measure(Constraints::loose(Size::new(100.0, 100.0)));
319 assert_eq!(size, Size::ZERO);
320 }
321
322 #[test]
323 fn test_row_builder() {
324 let row = Row::new()
325 .gap(10.0)
326 .main_axis_alignment(MainAxisAlignment::Center)
327 .cross_axis_alignment(CrossAxisAlignment::Start)
328 .with_test_id("my-row");
329
330 assert_eq!(row.gap, 10.0);
331 assert_eq!(row.main_axis_alignment, MainAxisAlignment::Center);
332 assert_eq!(row.cross_axis_alignment, CrossAxisAlignment::Start);
333 assert_eq!(Widget::test_id(&row), Some("my-row"));
334 }
335
336 #[test]
337 fn test_row_default() {
338 let row = Row::default();
339 assert_eq!(row.main_axis_alignment, MainAxisAlignment::Start);
340 assert_eq!(row.cross_axis_alignment, CrossAxisAlignment::Center);
341 assert_eq!(row.gap, 0.0);
342 }
343
344 #[test]
345 fn test_row_type_id() {
346 let row = Row::new();
347 assert_eq!(Widget::type_id(&row), TypeId::of::<Row>());
348 }
349
350 #[test]
351 fn test_row_children() {
352 let row = Row::new()
353 .child(FixedWidget::new(50.0, 30.0))
354 .child(FixedWidget::new(50.0, 30.0));
355 assert_eq!(row.children().len(), 2);
356 }
357
358 #[test]
361 fn test_row_measure_single_child() {
362 let row = Row::new().child(FixedWidget::new(50.0, 30.0));
363 let size = row.measure(Constraints::loose(Size::new(200.0, 100.0)));
364 assert_eq!(size, Size::new(50.0, 30.0));
365 }
366
367 #[test]
368 fn test_row_measure_multiple_children() {
369 let row = Row::new()
370 .child(FixedWidget::new(50.0, 30.0))
371 .child(FixedWidget::new(60.0, 40.0));
372 let size = row.measure(Constraints::loose(Size::new(200.0, 100.0)));
373 assert_eq!(size, Size::new(110.0, 40.0));
374 }
375
376 #[test]
377 fn test_row_measure_with_gap() {
378 let row = Row::new()
379 .gap(10.0)
380 .child(FixedWidget::new(50.0, 30.0))
381 .child(FixedWidget::new(50.0, 30.0));
382 let size = row.measure(Constraints::loose(Size::new(200.0, 100.0)));
383 assert_eq!(size, Size::new(110.0, 30.0)); }
385
386 #[test]
387 fn test_row_measure_constrained() {
388 let row = Row::new()
389 .child(FixedWidget::new(100.0, 50.0))
390 .child(FixedWidget::new(100.0, 50.0));
391 let size = row.measure(Constraints::tight(Size::new(150.0, 40.0)));
392 assert_eq!(size, Size::new(150.0, 40.0)); }
394
395 #[test]
398 fn test_row_alignment_start() {
399 let mut row = Row::new()
400 .main_axis_alignment(MainAxisAlignment::Start)
401 .child(FixedWidget::new(30.0, 20.0))
402 .child(FixedWidget::new(30.0, 20.0));
403
404 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
405
406 assert_eq!(row.child_bounds.len(), 2);
407 assert_eq!(row.child_bounds[0].x, 0.0);
408 assert_eq!(row.child_bounds[1].x, 30.0);
409 }
410
411 #[test]
412 fn test_row_alignment_end() {
413 let mut row = Row::new()
414 .main_axis_alignment(MainAxisAlignment::End)
415 .child(FixedWidget::new(30.0, 20.0))
416 .child(FixedWidget::new(30.0, 20.0));
417
418 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
419
420 assert_eq!(row.child_bounds[0].x, 140.0);
422 assert_eq!(row.child_bounds[1].x, 170.0);
423 }
424
425 #[test]
426 fn test_row_alignment_center() {
427 let mut row = Row::new()
428 .main_axis_alignment(MainAxisAlignment::Center)
429 .child(FixedWidget::new(30.0, 20.0))
430 .child(FixedWidget::new(30.0, 20.0));
431
432 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
433
434 assert_eq!(row.child_bounds[0].x, 70.0);
436 assert_eq!(row.child_bounds[1].x, 100.0);
437 }
438
439 #[test]
440 fn test_row_alignment_space_between() {
441 let mut row = Row::new()
442 .main_axis_alignment(MainAxisAlignment::SpaceBetween)
443 .child(FixedWidget::new(30.0, 20.0))
444 .child(FixedWidget::new(30.0, 20.0));
445
446 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
447
448 assert_eq!(row.child_bounds[0].x, 0.0);
450 assert_eq!(row.child_bounds[1].x, 170.0); }
452
453 #[test]
454 fn test_row_alignment_space_between_single_child() {
455 let mut row = Row::new()
456 .main_axis_alignment(MainAxisAlignment::SpaceBetween)
457 .child(FixedWidget::new(30.0, 20.0));
458
459 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
460
461 assert_eq!(row.child_bounds[0].x, 0.0);
463 }
464
465 #[test]
466 fn test_row_alignment_space_between_three_children() {
467 let mut row = Row::new()
468 .main_axis_alignment(MainAxisAlignment::SpaceBetween)
469 .child(FixedWidget::new(30.0, 20.0))
470 .child(FixedWidget::new(30.0, 20.0))
471 .child(FixedWidget::new(30.0, 20.0));
472
473 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
474
475 assert_eq!(row.child_bounds[0].x, 0.0);
477 assert_eq!(row.child_bounds[1].x, 85.0); assert_eq!(row.child_bounds[2].x, 170.0); }
480
481 #[test]
482 fn test_row_alignment_space_around() {
483 let mut row = Row::new()
484 .main_axis_alignment(MainAxisAlignment::SpaceAround)
485 .child(FixedWidget::new(40.0, 20.0))
486 .child(FixedWidget::new(40.0, 20.0));
487
488 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
489
490 assert_eq!(row.child_bounds[0].x, 30.0);
493 assert_eq!(row.child_bounds[1].x, 130.0);
494 }
495
496 #[test]
497 fn test_row_alignment_space_evenly() {
498 let mut row = Row::new()
499 .main_axis_alignment(MainAxisAlignment::SpaceEvenly)
500 .child(FixedWidget::new(40.0, 20.0))
501 .child(FixedWidget::new(40.0, 20.0));
502
503 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
504
505 assert_eq!(row.child_bounds[0].x, 40.0);
508 assert_eq!(row.child_bounds[1].x, 120.0);
509 }
510
511 #[test]
514 fn test_row_cross_alignment_start() {
515 let mut row = Row::new()
516 .cross_axis_alignment(CrossAxisAlignment::Start)
517 .child(FixedWidget::new(30.0, 20.0));
518
519 row.layout(Rect::new(0.0, 0.0, 200.0, 100.0));
520
521 assert_eq!(row.child_bounds[0].y, 0.0);
522 assert_eq!(row.child_bounds[0].height, 20.0);
523 }
524
525 #[test]
526 fn test_row_cross_alignment_end() {
527 let mut row = Row::new()
528 .cross_axis_alignment(CrossAxisAlignment::End)
529 .child(FixedWidget::new(30.0, 20.0));
530
531 row.layout(Rect::new(0.0, 0.0, 200.0, 100.0));
532
533 assert_eq!(row.child_bounds[0].y, 80.0); assert_eq!(row.child_bounds[0].height, 20.0);
535 }
536
537 #[test]
538 fn test_row_cross_alignment_center() {
539 let mut row = Row::new()
540 .cross_axis_alignment(CrossAxisAlignment::Center)
541 .child(FixedWidget::new(30.0, 20.0));
542
543 row.layout(Rect::new(0.0, 0.0, 200.0, 100.0));
544
545 assert_eq!(row.child_bounds[0].y, 40.0); assert_eq!(row.child_bounds[0].height, 20.0);
547 }
548
549 #[test]
550 fn test_row_cross_alignment_stretch() {
551 let mut row = Row::new()
552 .cross_axis_alignment(CrossAxisAlignment::Stretch)
553 .child(FixedWidget::new(30.0, 20.0));
554
555 row.layout(Rect::new(0.0, 0.0, 200.0, 100.0));
556
557 assert_eq!(row.child_bounds[0].y, 0.0);
558 assert_eq!(row.child_bounds[0].height, 100.0); }
560
561 #[test]
564 fn test_row_gap_single_child() {
565 let mut row = Row::new().gap(20.0).child(FixedWidget::new(30.0, 20.0));
566
567 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
568
569 assert_eq!(row.child_bounds[0].x, 0.0);
571 }
572
573 #[test]
574 fn test_row_gap_multiple_children() {
575 let mut row = Row::new()
576 .gap(15.0)
577 .child(FixedWidget::new(30.0, 20.0))
578 .child(FixedWidget::new(30.0, 20.0))
579 .child(FixedWidget::new(30.0, 20.0));
580
581 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
582
583 assert_eq!(row.child_bounds[0].x, 0.0);
584 assert_eq!(row.child_bounds[1].x, 45.0); assert_eq!(row.child_bounds[2].x, 90.0); }
587
588 #[test]
589 fn test_row_gap_with_alignment_center() {
590 let mut row = Row::new()
591 .gap(10.0)
592 .main_axis_alignment(MainAxisAlignment::Center)
593 .child(FixedWidget::new(30.0, 20.0))
594 .child(FixedWidget::new(30.0, 20.0));
595
596 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
597
598 assert_eq!(row.child_bounds[0].x, 65.0);
600 assert_eq!(row.child_bounds[1].x, 105.0); }
602
603 #[test]
606 fn test_row_layout_empty() {
607 let mut row = Row::new();
608 let result = row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
609 assert_eq!(result.size, Size::ZERO);
610 }
611
612 #[test]
613 fn test_row_content_larger_than_bounds() {
614 let mut row = Row::new()
615 .child(FixedWidget::new(100.0, 30.0))
616 .child(FixedWidget::new(100.0, 30.0))
617 .child(FixedWidget::new(100.0, 30.0));
618
619 row.layout(Rect::new(0.0, 0.0, 200.0, 50.0));
621
622 assert_eq!(row.child_bounds[0].x, 0.0);
624 assert_eq!(row.child_bounds[1].x, 100.0);
625 assert_eq!(row.child_bounds[2].x, 200.0);
626 }
627
628 #[test]
629 fn test_row_with_offset_bounds() {
630 let mut row = Row::new()
631 .child(FixedWidget::new(30.0, 20.0))
632 .child(FixedWidget::new(30.0, 20.0));
633
634 row.layout(Rect::new(50.0, 25.0, 200.0, 50.0));
635
636 assert_eq!(row.child_bounds[0].x, 50.0);
638 assert_eq!(row.child_bounds[0].y, 40.0); assert_eq!(row.child_bounds[1].x, 80.0);
640 }
641
642 #[test]
643 fn test_row_varying_child_heights() {
644 let mut row = Row::new()
645 .cross_axis_alignment(CrossAxisAlignment::Center)
646 .child(FixedWidget::new(30.0, 20.0))
647 .child(FixedWidget::new(30.0, 60.0))
648 .child(FixedWidget::new(30.0, 40.0));
649
650 row.layout(Rect::new(0.0, 0.0, 200.0, 100.0));
651
652 assert_eq!(row.child_bounds[0].y, 40.0); assert_eq!(row.child_bounds[1].y, 20.0); assert_eq!(row.child_bounds[2].y, 30.0); }
657
658 #[test]
661 fn test_main_axis_alignment_default() {
662 assert_eq!(MainAxisAlignment::default(), MainAxisAlignment::Start);
663 }
664
665 #[test]
666 fn test_cross_axis_alignment_default() {
667 assert_eq!(CrossAxisAlignment::default(), CrossAxisAlignment::Center);
668 }
669}