1use std::collections::HashMap;
2use crate::types::rect::Rect;
3use crate::types::state::WidgetId;
4use super::types::*;
5
6#[derive(Clone, Debug)]
7pub struct LayoutTree {
8 pub root: LayoutNode,
9 pub computed: HashMap<WidgetId, LayoutComputed>,
10}
11
12impl LayoutTree {
13 pub fn new(root: LayoutNode) -> Self {
14 Self {
15 root,
16 computed: HashMap::new(),
17 }
18 }
19
20 pub fn compute(&mut self, viewport: Rect) {
22 self.computed.clear();
23
24 let mut ctx = LayoutContext {
25 computed: &mut self.computed,
26 };
27
28 layout_node_at(&self.root, viewport, &mut ctx, 0, None);
30 }
31
32 pub fn get_rect(&self, id: &WidgetId) -> Option<Rect> {
33 self.computed.get(id).map(|c| c.rect)
34 }
35
36 pub fn get_computed(&self, id: &WidgetId) -> Option<&LayoutComputed> {
37 self.computed.get(id)
38 }
39}
40
41struct LayoutContext<'a> {
42 computed: &'a mut HashMap<WidgetId, LayoutComputed>,
43}
44
45fn layout_node_at(
46 node: &LayoutNode,
47 target_rect: Rect,
48 ctx: &mut LayoutContext,
49 parent_z: i32,
50 parent_clip: Option<Rect>,
51) {
52 let margin = &node.style.margin;
54 let x = target_rect.min_x() + margin.left + node.style.offset_x;
55 let y = target_rect.min_y() + margin.top + node.style.offset_y;
56
57 let width = target_rect.width() - margin.width();
63 let height = target_rect.height() - margin.height();
64
65 let final_rect = Rect::new(x, y, width.max(0.0), height.max(0.0));
66
67 let padding = &node.style.padding;
69 let content_x = x + padding.left;
70 let content_y = y + padding.top;
71 let content_w = (width - padding.width()).max(0.0);
72 let content_h = (height - padding.height()).max(0.0);
73 let content_rect = Rect::new(content_x, content_y, content_w, content_h);
74
75 let mut current_clip = parent_clip;
77 if node.flags.contains(LayoutFlags::CLIP_CONTENT) {
78 current_clip = match current_clip {
79 Some(parent) => Some(parent.intersect(content_rect)), None => Some(content_rect),
81 };
82 }
83
84 let z_order = parent_z + node.style.z_index;
86
87 ctx.computed.insert(node.id.clone(), LayoutComputed {
89 rect: final_rect,
90 content_rect,
91 clip_rect: current_clip,
92 z_order,
93 });
94
95 match node.style.display {
97 Display::Flex => layout_flex(node, content_rect, ctx, z_order, current_clip),
98 Display::Stack => layout_stack(node, content_rect, ctx, z_order, current_clip),
99 Display::None => {}, Display::Grid => layout_flex(node, content_rect, ctx, z_order, current_clip), }
102}
103
104fn layout_flex(
105 node: &LayoutNode,
106 content_rect: Rect,
107 ctx: &mut LayoutContext,
108 z: i32,
109 clip: Option<Rect>,
110) {
111 let dir = node.style.direction;
112 let gap = node.style.gap;
113
114 let mut total_fixed = 0.0;
120 let mut fill_count = 0;
121
122 for child in &node.children {
123 if child.style.display == Display::None || child.style.position == Position::Absolute { continue; }
124
125 let size_spec = match dir {
126 FlexDirection::Row => child.style.width,
127 FlexDirection::Column => child.style.height,
128 };
129
130 match size_spec {
131 SizeSpec::Fix(v) => total_fixed += v,
132 SizeSpec::Pct(p) => {
133 let basis = match dir {
134 FlexDirection::Row => content_rect.width(),
135 FlexDirection::Column => content_rect.height(),
136 };
137 total_fixed += basis * p;
138 },
139 SizeSpec::Fill => fill_count += 1,
140 SizeSpec::Content => {
141 }
146 }
147 }
148
149 let visible_children = node.children.iter().filter(|c| c.style.display != Display::None && c.style.position != Position::Absolute).count();
151 let total_gap = if visible_children > 1 { (visible_children - 1) as f64 * gap } else { 0.0 };
152 total_fixed += total_gap;
153
154 let available_space = match dir {
156 FlexDirection::Row => content_rect.width(),
157 FlexDirection::Column => content_rect.height(),
158 };
159
160 let remaining = (available_space - total_fixed).max(0.0);
161 let flex_unit = if fill_count > 0 { remaining / fill_count as f64 } else { 0.0 };
162
163 let mut cursor = match dir {
165 FlexDirection::Row => content_rect.min_x(),
166 FlexDirection::Column => content_rect.min_y(),
167 };
168
169 for child in &node.children {
170 if child.style.display == Display::None { continue; }
171
172 if child.style.position == Position::Absolute {
174 layout_node_at(child, content_rect, ctx, z + 100, clip);
179 continue;
180 }
181
182 let (child_w, child_h) = match dir {
183 FlexDirection::Row => {
184 let w = match child.style.width {
185 SizeSpec::Fix(v) => v,
186 SizeSpec::Pct(p) => content_rect.width() * p,
187 SizeSpec::Fill => flex_unit,
188 SizeSpec::Content => 100.0, };
190 let h = match child.style.height {
191 SizeSpec::Fix(v) => v,
192 SizeSpec::Pct(p) => content_rect.height() * p,
193 SizeSpec::Fill => content_rect.height(),
194 SizeSpec::Content => content_rect.height(), };
196 (w, h)
197 },
198 FlexDirection::Column => {
199 let h = match child.style.height {
200 SizeSpec::Fix(v) => v,
201 SizeSpec::Pct(p) => content_rect.height() * p,
202 SizeSpec::Fill => flex_unit,
203 SizeSpec::Content => 30.0, };
205 let w = match child.style.width {
206 SizeSpec::Fix(v) => v,
207 SizeSpec::Pct(p) => content_rect.width() * p,
208 SizeSpec::Fill => content_rect.width(),
209 SizeSpec::Content => content_rect.width(), };
211 (w, h)
212 }
213 };
214
215 let child_x = match dir {
216 FlexDirection::Row => cursor,
217 FlexDirection::Column => content_rect.min_x(),
218 };
219 let child_y = match dir {
220 FlexDirection::Row => content_rect.min_y(),
221 FlexDirection::Column => cursor,
222 };
223
224 let child_rect = Rect::new(child_x, child_y, child_w, child_h);
225 layout_node_at(child, child_rect, ctx, z, clip);
226
227 match dir {
229 FlexDirection::Row => cursor += child_w + gap,
230 FlexDirection::Column => cursor += child_h + gap,
231 }
232 }
233}
234
235fn layout_stack(
236 node: &LayoutNode,
237 content_rect: Rect,
238 ctx: &mut LayoutContext,
239 z: i32,
240 clip: Option<Rect>,
241) {
242 for (i, child) in node.children.iter().enumerate() {
244 if child.style.display == Display::None { continue; }
245
246 let child_z = z + child.style.z_index + (i as i32);
248
249 layout_node_at(child, content_rect, ctx, child_z, clip);
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_flex_column_layout() {
259 let header = LayoutNode::new("header")
264 .with_style(LayoutStyle {
265 height: SizeSpec::Fix(50.0),
266 width: SizeSpec::Fill,
267 ..Default::default()
268 });
269
270 let content = LayoutNode::new("content")
271 .with_style(LayoutStyle {
272 height: SizeSpec::Fill,
273 width: SizeSpec::Fill,
274 ..Default::default()
275 });
276
277 let root = LayoutNode::new("root")
278 .with_style(LayoutStyle {
279 display: Display::Flex,
280 direction: FlexDirection::Column,
281 width: SizeSpec::Fix(200.0),
282 height: SizeSpec::Fix(200.0),
283 ..Default::default()
284 })
285 .with_child(header)
286 .with_child(content);
287
288 let mut tree = LayoutTree::new(root);
289 let viewport = Rect::new(0.0, 0.0, 200.0, 200.0);
290
291 tree.compute(viewport);
292
293 let root_rect = tree.get_rect(&WidgetId::new("root")).unwrap();
295 assert_eq!(root_rect.width, 200.0);
296 assert_eq!(root_rect.height, 200.0);
297
298 let header_rect = tree.get_rect(&WidgetId::new("header")).unwrap();
300 assert_eq!(header_rect.y, 0.0);
301 assert_eq!(header_rect.height, 50.0);
302 assert_eq!(header_rect.width, 200.0);
303
304 let content_rect = tree.get_rect(&WidgetId::new("content")).unwrap();
306 assert_eq!(content_rect.y, 50.0);
307 assert_eq!(content_rect.height, 150.0); assert_eq!(content_rect.width, 200.0);
309 }
310
311 #[test]
312 fn test_flex_row_layout_with_gap() {
313 let left = LayoutNode::new("left")
318 .with_style(LayoutStyle {
319 width: SizeSpec::Fix(50.0),
320 height: SizeSpec::Fill,
321 ..Default::default()
322 });
323
324 let right = LayoutNode::new("right")
325 .with_style(LayoutStyle {
326 width: SizeSpec::Fill,
327 height: SizeSpec::Fill,
328 ..Default::default()
329 });
330
331 let root = LayoutNode::new("root")
332 .with_style(LayoutStyle {
333 display: Display::Flex,
334 direction: FlexDirection::Row,
335 gap: 10.0,
336 width: SizeSpec::Fix(200.0),
337 height: SizeSpec::Fix(100.0),
338 ..Default::default()
339 })
340 .with_child(left)
341 .with_child(right);
342
343 let mut tree = LayoutTree::new(root);
344 let viewport = Rect::new(0.0, 0.0, 200.0, 100.0);
345
346 tree.compute(viewport);
347
348 let left_rect = tree.get_rect(&WidgetId::new("left")).unwrap();
349 assert_eq!(left_rect.x, 0.0);
350 assert_eq!(left_rect.width, 50.0);
351
352 let right_rect = tree.get_rect(&WidgetId::new("right")).unwrap();
353 assert_eq!(right_rect.x, 60.0); assert_eq!(right_rect.width, 140.0); }
356}