1use 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::layout_engine::{apply_align, flex_layout};
9
10pub struct RowComponent;
15
16impl TuiComponent for RowComponent {
17 fn name(&self) -> &'static str {
18 "Row"
19 }
20
21 fn render(
22 &self,
23 ctx: &ComponentContext,
24 area: Rect,
25 frame: &mut Frame,
26 render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
27 measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
28 ) {
29 let comp_model = match ctx.components.get(&ctx.component_id) {
30 Some(m) => m,
31 None => return,
32 };
33
34 let children = match comp_model.children() {
35 Some(c) => c,
36 None => return,
37 };
38
39 let justify = comp_model.get_property::<Justify>("justify").unwrap_or(Justify::Start);
40 let align = comp_model.get_property::<Align>("align").unwrap_or(Align::Start);
41
42 match children {
43 ChildList::Static(ids) => {
44 render_static_children(
45 ctx, area, frame, render_child, measure_child,
46 &ids, justify, align, Direction::Horizontal,
47 );
48 }
49 ChildList::Template { component_id, path } => {
50 render_template_children(
51 ctx, area, frame, render_child, measure_child,
52 &component_id, &path, justify, align, Direction::Horizontal,
53 );
54 }
55 }
56 }
57
58 fn natural_height(
60 &self,
61 ctx: &ComponentContext,
62 available_width: u16,
63 measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
64 ) -> Option<u16> {
65 let comp_model = ctx.components.get(&ctx.component_id)?;
66 let ids = match comp_model.children()? {
67 ChildList::Static(ids) => ids,
68 ChildList::Template { component_id, path } => {
69 let count = match ctx.data_context.get(&path) {
70 Some(serde_json::Value::Array(arr)) => arr.len(),
71 _ => return None,
72 };
73 if count == 0 {
74 return Some(0);
75 }
76 let item_path = format!("{}/{}", path, 0);
77 return measure_child(&component_id, &item_path, available_width);
78 }
79 };
80 if ids.is_empty() {
81 return Some(0);
82 }
83 let base = ctx.data_context.base_path();
86 let mut max: u16 = 0;
87 for id in &ids {
88 max = max.max(measure_child(id, base, available_width)?);
89 }
90 Some(max)
91 }
92}
93
94pub(crate) fn render_static_children(
102 ctx: &ComponentContext,
103 area: Rect,
104 frame: &mut Frame,
105 render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
106 measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
107 ids: &[String],
108 justify: Justify,
109 align: Align,
110 direction: Direction,
111) {
112 if ids.is_empty() {
113 return;
114 }
115
116 let base = ctx.data_context.base_path().to_string();
120
121 let items: Vec<(Option<u16>, Option<f64>)> = ids
124 .iter()
125 .map(|id| {
126 let weight = ctx.components.get(id).and_then(|m| m.weight());
127 let natural = match direction {
128 Direction::Vertical => measure_child(id, &base, area.width),
129 Direction::Horizontal => None,
130 };
131 (natural, weight)
132 })
133 .collect();
134
135 let rects = flex_layout(direction, area, &items, justify);
136
137 for (i, child_id) in ids.iter().enumerate() {
139 let child_area = apply_align(align, rects[i], area, direction);
140 render_child(child_id, child_area, frame, &base);
141 }
142}
143
144pub(crate) fn render_template_children(
146 ctx: &ComponentContext,
147 area: Rect,
148 frame: &mut Frame,
149 render_child: &mut dyn FnMut(&str, Rect, &mut Frame, &str),
150 measure_child: &mut dyn FnMut(&str, &str, u16) -> Option<u16>,
151 component_id: &str,
152 path: &str,
153 justify: Justify,
154 align: Align,
155 direction: Direction,
156) {
157 let array = match ctx.data_context.get(path) {
159 Some(serde_json::Value::Array(arr)) => arr,
160 _ => return,
161 };
162
163 let count = array.len();
164 if count == 0 {
165 return;
166 }
167
168 let items: Vec<(Option<u16>, Option<f64>)> = (0..count)
171 .map(|i| {
172 let item_path = format!("{}/{}", path, i);
173 let natural = match direction {
174 Direction::Vertical => measure_child(component_id, &item_path, area.width),
175 Direction::Horizontal => None,
176 };
177 (natural, None)
179 })
180 .collect();
181
182 let rects = flex_layout(direction, area, &items, justify);
183
184 for i in 0..count {
185 let child_area = apply_align(align, rects[i], area, direction);
186 let item_path = format!("{}/{}", path, i);
188 render_child(component_id, child_area, frame, &item_path);
189 }
190}