Skip to main content

a2ui_tui/components/
column.rs

1//! Column component — vertical layout container.
2
3use ratatui::{Frame, layout::{Direction, Rect}};
4
5use a2ui_base::model::component_context::ComponentContext;
6use a2ui_base::protocol::common_types::{Align, ChildList, Justify};
7use crate::component_impl::TuiComponent;
8use crate::components::row::{render_static_children, render_template_children};
9
10/// Column component implementation.
11///
12/// Lays out children vertically using weighted splitting.
13/// Invisible container — no margin or padding.
14/// Default justify: Start, default align: Stretch.
15pub struct ColumnComponent;
16
17impl TuiComponent for ColumnComponent {
18    fn name(&self) -> &'static str {
19        "Column"
20    }
21
22    fn render(
23        &self,
24        ctx: &ComponentContext,
25        area: Rect,
26        frame: &mut Frame,
27        render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
28        measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
29    ) {
30        let comp_model = match ctx.components.get(&ctx.component_id) {
31            Some(m) => m,
32            None => return,
33        };
34
35        let children = match comp_model.children() {
36            Some(c) => c,
37            None => return,
38        };
39
40        let justify = comp_model.get_property::<Justify>("justify").unwrap_or(Justify::Start);
41        let align = comp_model.get_property::<Align>("align").unwrap_or(Align::Stretch);
42
43        match children {
44            ChildList::Static(ids) => {
45                render_static_children(
46                    ctx, area, frame, render_child, measure_child,
47                    &ids, justify, align, Direction::Vertical,
48                );
49            }
50            ChildList::Template { component_id, path } => {
51                render_template_children(
52                    ctx, area, frame, render_child, measure_child,
53                    &component_id, &path, justify, align, Direction::Vertical,
54                );
55            }
56        }
57    }
58
59    /// Natural height = sum of children's natural heights (vertical stack).
60    /// `None` if there are no children or any child reports `None` (conservative —
61    /// a container with an unmeasured child cannot give a definite height).
62    fn natural_height(
63        &self,
64        ctx: &ComponentContext,
65        available_width: u16,
66        measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
67    ) -> Option<u16> {
68        let comp_model = ctx.components.get(&ctx.component_id)?;
69        let ids = match comp_model.children()? {
70            ChildList::Static(ids) => ids,
71            ChildList::Template { component_id, path } => {
72                let count = match ctx.data_context.get(&path) {
73                    Some(serde_json::Value::Array(arr)) => arr.len(),
74                    _ => return None,
75                };
76                if count == 0 {
77                    return Some(0);
78                }
79                // All instances share one component_id; measure the first at its path
80                // and multiply (heights are structurally identical).
81                let item_path = format!("{}/{}", path, 0);
82                let one = measure_child(&component_id, &item_path, available_width)?;
83                return Some(one.saturating_mul(count as u16));
84            }
85        };
86        if ids.is_empty() {
87            return Some(0);
88        }
89        // Static children inherit this component's base path (matters when this
90        // component is itself a template instance rendered at a nested path).
91        let base = ctx.data_context.base_path();
92        let mut sum: u16 = 0;
93        for id in &ids {
94            sum = sum.saturating_add(measure_child(id, base, available_width)?);
95        }
96        Some(sum)
97    }
98}