1use presentar_core::{
4 widget::LayoutResult, Canvas, Constraints, Event, Rect, Size, TypeId, Widget,
5};
6use serde::{Deserialize, Serialize};
7use std::any::Any;
8
9use crate::row::{CrossAxisAlignment, MainAxisAlignment};
10
11#[derive(Serialize, Deserialize)]
13pub struct Column {
14 main_axis_alignment: MainAxisAlignment,
16 cross_axis_alignment: CrossAxisAlignment,
18 gap: f32,
20 #[serde(skip)]
22 children: Vec<Box<dyn Widget>>,
23 test_id_value: Option<String>,
25 #[serde(skip)]
27 bounds: Rect,
28 #[serde(skip)]
30 child_bounds: Vec<Rect>,
31}
32
33impl Default for Column {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl Column {
40 #[must_use]
42 pub fn new() -> Self {
43 Self {
44 main_axis_alignment: MainAxisAlignment::Start,
45 cross_axis_alignment: CrossAxisAlignment::Center,
46 gap: 0.0,
47 children: Vec::new(),
48 test_id_value: None,
49 bounds: Rect::default(),
50 child_bounds: Vec::new(),
51 }
52 }
53
54 #[must_use]
56 pub const fn main_axis_alignment(mut self, alignment: MainAxisAlignment) -> Self {
57 self.main_axis_alignment = alignment;
58 self
59 }
60
61 #[must_use]
63 pub const fn cross_axis_alignment(mut self, alignment: CrossAxisAlignment) -> Self {
64 self.cross_axis_alignment = alignment;
65 self
66 }
67
68 #[must_use]
70 pub const fn gap(mut self, gap: f32) -> Self {
71 self.gap = gap;
72 self
73 }
74
75 pub fn child(mut self, widget: impl Widget + 'static) -> Self {
77 self.children.push(Box::new(widget));
78 self
79 }
80
81 #[must_use]
83 pub fn with_test_id(mut self, id: impl Into<String>) -> Self {
84 self.test_id_value = Some(id.into());
85 self
86 }
87}
88
89impl Widget for Column {
90 fn type_id(&self) -> TypeId {
91 TypeId::of::<Self>()
92 }
93
94 fn measure(&self, constraints: Constraints) -> Size {
95 if self.children.is_empty() {
96 return Size::ZERO;
97 }
98
99 let mut max_width = 0.0f32;
100 let mut total_height = 0.0f32;
101
102 for (i, child) in self.children.iter().enumerate() {
104 let child_constraints = Constraints::new(
105 0.0,
106 constraints.max_width,
107 0.0,
108 (constraints.max_height - total_height).max(0.0),
109 );
110
111 let child_size = child.measure(child_constraints);
112 max_width = max_width.max(child_size.width);
113 total_height += child_size.height;
114
115 if i < self.children.len() - 1 {
116 total_height += self.gap;
117 }
118 }
119
120 constraints.constrain(Size::new(max_width, total_height))
121 }
122
123 fn layout(&mut self, bounds: Rect) -> LayoutResult {
124 self.bounds = bounds;
125 self.child_bounds.clear();
126
127 if self.children.is_empty() {
128 return LayoutResult { size: Size::ZERO };
129 }
130
131 let mut child_sizes: Vec<Size> = Vec::with_capacity(self.children.len());
133 let mut total_height = 0.0f32;
134
135 for child in &self.children {
136 let child_constraints = Constraints::loose(bounds.size());
137 let size = child.measure(child_constraints);
138 total_height += size.height;
139 child_sizes.push(size);
140 }
141
142 let gaps_height = self.gap * (self.children.len() - 1).max(0) as f32;
143 let content_height = total_height + gaps_height;
144 let remaining_space = (bounds.height - content_height).max(0.0);
145
146 let (mut y, extra_gap) = match self.main_axis_alignment {
148 MainAxisAlignment::Start => (bounds.y, 0.0),
149 MainAxisAlignment::End => (bounds.y + remaining_space, 0.0),
150 MainAxisAlignment::Center => (bounds.y + remaining_space / 2.0, 0.0),
151 MainAxisAlignment::SpaceBetween => {
152 if self.children.len() > 1 {
153 (bounds.y, remaining_space / (self.children.len() - 1) as f32)
154 } else {
155 (bounds.y, 0.0)
156 }
157 }
158 MainAxisAlignment::SpaceAround => {
159 let gap = remaining_space / self.children.len() as f32;
160 (bounds.y + gap / 2.0, gap)
161 }
162 MainAxisAlignment::SpaceEvenly => {
163 let gap = remaining_space / (self.children.len() + 1) as f32;
164 (bounds.y + gap, gap)
165 }
166 };
167
168 let num_children = self.children.len();
170 for (i, (child, size)) in self.children.iter_mut().zip(child_sizes.iter()).enumerate() {
171 let x = match self.cross_axis_alignment {
172 CrossAxisAlignment::Start | CrossAxisAlignment::Stretch => bounds.x,
173 CrossAxisAlignment::End => bounds.x + bounds.width - size.width,
174 CrossAxisAlignment::Center => bounds.x + (bounds.width - size.width) / 2.0,
175 };
176
177 let width = if self.cross_axis_alignment == CrossAxisAlignment::Stretch {
178 bounds.width
179 } else {
180 size.width
181 };
182
183 let child_bounds = Rect::new(x, y, width, size.height);
184 child.layout(child_bounds);
185 self.child_bounds.push(child_bounds);
186
187 if i < num_children - 1 {
189 y += size.height;
190 if self.main_axis_alignment == MainAxisAlignment::SpaceBetween {
191 y += extra_gap;
193 } else {
194 y += self.gap + extra_gap;
195 }
196 }
197 }
198
199 LayoutResult {
200 size: bounds.size(),
201 }
202 }
203
204 fn paint(&self, canvas: &mut dyn Canvas) {
205 for child in &self.children {
206 child.paint(canvas);
207 }
208 }
209
210 fn event(&mut self, event: &Event) -> Option<Box<dyn Any + Send>> {
211 for child in &mut self.children {
212 if let Some(msg) = child.event(event) {
213 return Some(msg);
214 }
215 }
216 None
217 }
218
219 fn children(&self) -> &[Box<dyn Widget>] {
220 &self.children
221 }
222
223 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
224 &mut self.children
225 }
226
227 fn test_id(&self) -> Option<&str> {
228 self.test_id_value.as_deref()
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use presentar_core::widget::AccessibleRole;
236 use presentar_core::Widget;
237
238 struct FixedWidget {
240 size: Size,
241 }
242
243 impl FixedWidget {
244 fn new(width: f32, height: f32) -> Self {
245 Self {
246 size: Size::new(width, height),
247 }
248 }
249 }
250
251 impl Widget for FixedWidget {
252 fn type_id(&self) -> TypeId {
253 TypeId::of::<Self>()
254 }
255
256 fn measure(&self, constraints: Constraints) -> Size {
257 constraints.constrain(self.size)
258 }
259
260 fn layout(&mut self, _bounds: Rect) -> LayoutResult {
261 LayoutResult { size: self.size }
262 }
263
264 fn paint(&self, _canvas: &mut dyn Canvas) {}
265
266 fn event(&mut self, _event: &Event) -> Option<Box<dyn Any + Send>> {
267 None
268 }
269
270 fn children(&self) -> &[Box<dyn Widget>] {
271 &[]
272 }
273
274 fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
275 &mut []
276 }
277
278 fn accessible_role(&self) -> AccessibleRole {
279 AccessibleRole::Generic
280 }
281 }
282
283 #[test]
286 fn test_column_empty() {
287 let col = Column::new();
288 let size = col.measure(Constraints::loose(Size::new(100.0, 100.0)));
289 assert_eq!(size, Size::ZERO);
290 }
291
292 #[test]
293 fn test_column_builder() {
294 let col = Column::new()
295 .gap(10.0)
296 .main_axis_alignment(MainAxisAlignment::Center)
297 .cross_axis_alignment(CrossAxisAlignment::Start)
298 .with_test_id("my-column");
299
300 assert_eq!(col.gap, 10.0);
301 assert_eq!(col.main_axis_alignment, MainAxisAlignment::Center);
302 assert_eq!(col.cross_axis_alignment, CrossAxisAlignment::Start);
303 assert_eq!(Widget::test_id(&col), Some("my-column"));
304 }
305
306 #[test]
307 fn test_column_default() {
308 let col = Column::default();
309 assert_eq!(col.main_axis_alignment, MainAxisAlignment::Start);
310 assert_eq!(col.cross_axis_alignment, CrossAxisAlignment::Center);
311 assert_eq!(col.gap, 0.0);
312 }
313
314 #[test]
315 fn test_column_type_id() {
316 let col = Column::new();
317 assert_eq!(Widget::type_id(&col), TypeId::of::<Column>());
318 }
319
320 #[test]
321 fn test_column_children() {
322 let col = Column::new()
323 .child(FixedWidget::new(50.0, 30.0))
324 .child(FixedWidget::new(50.0, 30.0));
325 assert_eq!(col.children().len(), 2);
326 }
327
328 #[test]
331 fn test_column_measure_single_child() {
332 let col = Column::new().child(FixedWidget::new(50.0, 30.0));
333 let size = col.measure(Constraints::loose(Size::new(200.0, 200.0)));
334 assert_eq!(size, Size::new(50.0, 30.0));
335 }
336
337 #[test]
338 fn test_column_measure_multiple_children() {
339 let col = Column::new()
340 .child(FixedWidget::new(50.0, 30.0))
341 .child(FixedWidget::new(60.0, 40.0));
342 let size = col.measure(Constraints::loose(Size::new(200.0, 200.0)));
343 assert_eq!(size, Size::new(60.0, 70.0)); }
345
346 #[test]
347 fn test_column_measure_with_gap() {
348 let col = Column::new()
349 .gap(10.0)
350 .child(FixedWidget::new(50.0, 30.0))
351 .child(FixedWidget::new(50.0, 30.0));
352 let size = col.measure(Constraints::loose(Size::new(200.0, 200.0)));
353 assert_eq!(size, Size::new(50.0, 70.0)); }
355
356 #[test]
357 fn test_column_measure_constrained() {
358 let col = Column::new()
359 .child(FixedWidget::new(100.0, 100.0))
360 .child(FixedWidget::new(100.0, 100.0));
361 let size = col.measure(Constraints::tight(Size::new(80.0, 150.0)));
362 assert_eq!(size, Size::new(80.0, 150.0)); }
364
365 #[test]
368 fn test_column_alignment_start() {
369 let mut col = Column::new()
370 .main_axis_alignment(MainAxisAlignment::Start)
371 .child(FixedWidget::new(30.0, 20.0))
372 .child(FixedWidget::new(30.0, 20.0));
373
374 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
375
376 assert_eq!(col.child_bounds.len(), 2);
377 assert_eq!(col.child_bounds[0].y, 0.0);
378 assert_eq!(col.child_bounds[1].y, 20.0);
379 }
380
381 #[test]
382 fn test_column_alignment_end() {
383 let mut col = Column::new()
384 .main_axis_alignment(MainAxisAlignment::End)
385 .child(FixedWidget::new(30.0, 20.0))
386 .child(FixedWidget::new(30.0, 20.0));
387
388 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
389
390 assert_eq!(col.child_bounds[0].y, 160.0);
392 assert_eq!(col.child_bounds[1].y, 180.0);
393 }
394
395 #[test]
396 fn test_column_alignment_center() {
397 let mut col = Column::new()
398 .main_axis_alignment(MainAxisAlignment::Center)
399 .child(FixedWidget::new(30.0, 20.0))
400 .child(FixedWidget::new(30.0, 20.0));
401
402 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
403
404 assert_eq!(col.child_bounds[0].y, 80.0);
406 assert_eq!(col.child_bounds[1].y, 100.0);
407 }
408
409 #[test]
410 fn test_column_alignment_space_between() {
411 let mut col = Column::new()
412 .main_axis_alignment(MainAxisAlignment::SpaceBetween)
413 .child(FixedWidget::new(30.0, 20.0))
414 .child(FixedWidget::new(30.0, 20.0));
415
416 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
417
418 assert_eq!(col.child_bounds[0].y, 0.0);
420 assert_eq!(col.child_bounds[1].y, 180.0); }
422
423 #[test]
424 fn test_column_alignment_space_between_single_child() {
425 let mut col = Column::new()
426 .main_axis_alignment(MainAxisAlignment::SpaceBetween)
427 .child(FixedWidget::new(30.0, 20.0));
428
429 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
430
431 assert_eq!(col.child_bounds[0].y, 0.0);
433 }
434
435 #[test]
436 fn test_column_alignment_space_between_three_children() {
437 let mut col = Column::new()
438 .main_axis_alignment(MainAxisAlignment::SpaceBetween)
439 .child(FixedWidget::new(30.0, 20.0))
440 .child(FixedWidget::new(30.0, 20.0))
441 .child(FixedWidget::new(30.0, 20.0));
442
443 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
444
445 assert_eq!(col.child_bounds[0].y, 0.0);
447 assert_eq!(col.child_bounds[1].y, 90.0); assert_eq!(col.child_bounds[2].y, 180.0); }
450
451 #[test]
452 fn test_column_alignment_space_around() {
453 let mut col = Column::new()
454 .main_axis_alignment(MainAxisAlignment::SpaceAround)
455 .child(FixedWidget::new(30.0, 40.0))
456 .child(FixedWidget::new(30.0, 40.0));
457
458 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
459
460 assert_eq!(col.child_bounds[0].y, 30.0);
463 assert_eq!(col.child_bounds[1].y, 130.0);
464 }
465
466 #[test]
467 fn test_column_alignment_space_evenly() {
468 let mut col = Column::new()
469 .main_axis_alignment(MainAxisAlignment::SpaceEvenly)
470 .child(FixedWidget::new(30.0, 40.0))
471 .child(FixedWidget::new(30.0, 40.0));
472
473 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
474
475 assert_eq!(col.child_bounds[0].y, 40.0);
478 assert_eq!(col.child_bounds[1].y, 120.0);
479 }
480
481 #[test]
484 fn test_column_cross_alignment_start() {
485 let mut col = Column::new()
486 .cross_axis_alignment(CrossAxisAlignment::Start)
487 .child(FixedWidget::new(30.0, 20.0));
488
489 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
490
491 assert_eq!(col.child_bounds[0].x, 0.0);
492 assert_eq!(col.child_bounds[0].width, 30.0);
493 }
494
495 #[test]
496 fn test_column_cross_alignment_end() {
497 let mut col = Column::new()
498 .cross_axis_alignment(CrossAxisAlignment::End)
499 .child(FixedWidget::new(30.0, 20.0));
500
501 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
502
503 assert_eq!(col.child_bounds[0].x, 70.0); assert_eq!(col.child_bounds[0].width, 30.0);
505 }
506
507 #[test]
508 fn test_column_cross_alignment_center() {
509 let mut col = Column::new()
510 .cross_axis_alignment(CrossAxisAlignment::Center)
511 .child(FixedWidget::new(30.0, 20.0));
512
513 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
514
515 assert_eq!(col.child_bounds[0].x, 35.0); assert_eq!(col.child_bounds[0].width, 30.0);
517 }
518
519 #[test]
520 fn test_column_cross_alignment_stretch() {
521 let mut col = Column::new()
522 .cross_axis_alignment(CrossAxisAlignment::Stretch)
523 .child(FixedWidget::new(30.0, 20.0));
524
525 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
526
527 assert_eq!(col.child_bounds[0].x, 0.0);
528 assert_eq!(col.child_bounds[0].width, 100.0); }
530
531 #[test]
534 fn test_column_gap_single_child() {
535 let mut col = Column::new().gap(20.0).child(FixedWidget::new(30.0, 20.0));
536
537 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
538
539 assert_eq!(col.child_bounds[0].y, 0.0);
541 }
542
543 #[test]
544 fn test_column_gap_multiple_children() {
545 let mut col = Column::new()
546 .gap(15.0)
547 .child(FixedWidget::new(30.0, 20.0))
548 .child(FixedWidget::new(30.0, 20.0))
549 .child(FixedWidget::new(30.0, 20.0));
550
551 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
552
553 assert_eq!(col.child_bounds[0].y, 0.0);
554 assert_eq!(col.child_bounds[1].y, 35.0); assert_eq!(col.child_bounds[2].y, 70.0); }
557
558 #[test]
559 fn test_column_gap_with_alignment_center() {
560 let mut col = Column::new()
561 .gap(10.0)
562 .main_axis_alignment(MainAxisAlignment::Center)
563 .child(FixedWidget::new(30.0, 20.0))
564 .child(FixedWidget::new(30.0, 20.0));
565
566 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
567
568 assert_eq!(col.child_bounds[0].y, 75.0);
570 assert_eq!(col.child_bounds[1].y, 105.0); }
572
573 #[test]
576 fn test_column_layout_empty() {
577 let mut col = Column::new();
578 let result = col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
579 assert_eq!(result.size, Size::ZERO);
580 }
581
582 #[test]
583 fn test_column_content_larger_than_bounds() {
584 let mut col = Column::new()
585 .child(FixedWidget::new(30.0, 100.0))
586 .child(FixedWidget::new(30.0, 100.0))
587 .child(FixedWidget::new(30.0, 100.0));
588
589 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
591
592 assert_eq!(col.child_bounds[0].y, 0.0);
594 assert_eq!(col.child_bounds[1].y, 100.0);
595 assert_eq!(col.child_bounds[2].y, 200.0);
596 }
597
598 #[test]
599 fn test_column_with_offset_bounds() {
600 let mut col = Column::new()
601 .child(FixedWidget::new(30.0, 20.0))
602 .child(FixedWidget::new(30.0, 20.0));
603
604 col.layout(Rect::new(25.0, 50.0, 100.0, 200.0));
605
606 assert_eq!(col.child_bounds[0].y, 50.0);
608 assert_eq!(col.child_bounds[0].x, 60.0); assert_eq!(col.child_bounds[1].y, 70.0);
610 }
611
612 #[test]
613 fn test_column_varying_child_widths() {
614 let mut col = Column::new()
615 .cross_axis_alignment(CrossAxisAlignment::Center)
616 .child(FixedWidget::new(20.0, 30.0))
617 .child(FixedWidget::new(60.0, 30.0))
618 .child(FixedWidget::new(40.0, 30.0));
619
620 col.layout(Rect::new(0.0, 0.0, 100.0, 200.0));
621
622 assert_eq!(col.child_bounds[0].x, 40.0); assert_eq!(col.child_bounds[1].x, 20.0); assert_eq!(col.child_bounds[2].x, 30.0); }
627}