1use crate::LayoutEvent;
2use crate::geom::{Insets, Size};
3use crate::view::Align::{Center, End, Start};
4use crate::view::Flex::Resize;
5use crate::view::{Flex, ViewId};
6use Flex::Intrinsic;
7use log::info;
8
9pub fn layout_vbox(pass: &mut LayoutEvent) {
10 let Some(parent) = pass.scene.get_view_mut(&pass.target) else {
11 info!("view not found!");
12 return;
13 };
14 let h_flex = parent.h_flex.clone();
15 let padding = parent.padding.clone();
16 let mut available_space: Size = pass.space - parent.padding;
17
18 let fixed_kids = pass
20 .scene
21 .get_children_ids_filtered(&pass.target, |v| v.v_flex == Intrinsic);
22 for kid in &fixed_kids {
24 pass.layout_child(kid, available_space);
25 }
26
27 let kids_sum = fixed_kids.iter().fold(0, |a, id| {
29 return if let Some(view) = pass.scene.get_view_mut(id) {
30 view.bounds.size.h + a
31 } else {
32 a
33 };
34 });
35 let vert_leftover = (pass.space - padding).h - kids_sum;
36
37 let flex_kids = pass
39 .scene
40 .get_children_ids_filtered(&pass.target, |v| v.v_flex == Flex::Resize);
41 if flex_kids.len() > 0 {
42 let flex_space = Size {
43 w: pass.space.w - padding.left - padding.right,
44 h: vert_leftover / (flex_kids.len() as i32),
45 };
46 for kid in flex_kids {
47 pass.layout_child(&kid, flex_space);
48 }
49 }
50
51 let mut max_width = 0;
53 for kid in pass.scene.get_children_ids(&pass.target) {
54 if let Some(kid) = pass.scene.get_view_mut(&kid) {
55 max_width = max_width.max(kid.bounds.size.w);
56 }
57 }
58 if h_flex == Intrinsic {
59 available_space.w = max_width;
60 }
61
62 let mut y = padding.top;
64 let all_kids = pass.scene.get_children_ids(&pass.target);
65 let avail_w = available_space.w;
66 for kid in all_kids {
67 if let Some(kid) = pass.scene.get_view_mut(&kid) {
68 kid.bounds.position.x = match &kid.h_align {
69 Start => 0,
70 Center => (avail_w - kid.bounds.size.w) / 2,
71 End => (avail_w - kid.bounds.size.w),
72 } + padding.left;
73 kid.bounds.position.y = y;
74 y += kid.bounds.size.h;
75 }
76 }
77 if let Some(view) = pass.scene.get_view_mut(&pass.target) {
79 if view.h_flex == Resize {
80 view.bounds.size.w = pass.space.w
81 }
82 if view.h_flex == Intrinsic {
83 view.bounds.size.w = max_width + padding.left + padding.right
84 }
85 if view.v_flex == Resize {
86 view.bounds.size.h = pass.space.h
87 }
88 if view.v_flex == Intrinsic {}
89 }
90}
91
92pub fn layout_hbox(pass: &mut LayoutEvent) {
93 let Some(parent) = pass.scene.get_view_mut(&pass.target) else {
94 return;
95 };
96 let h_flex = parent.h_flex.clone();
97 let v_flex = parent.v_flex.clone();
98 if v_flex == Resize {
100 parent.bounds.size.h = pass.space.h
101 }
102 if h_flex == Resize {
103 parent.bounds.size.w = pass.space.w
104 }
105
106 let padding = parent.padding.clone();
107 let mut available_space = pass.space - padding;
108
109 let fixed_kids = pass
111 .scene
112 .get_children_ids_filtered(&pass.target, |v| v.h_flex == Intrinsic);
113
114 for kid in &fixed_kids {
116 pass.layout_child(kid, available_space);
117 }
118
119 let kids_sum: i32 = fixed_kids
121 .iter()
122 .map(|id| pass.scene.get_view(id))
123 .flatten()
124 .fold(0, |a, v| v.bounds.size.w + a);
125 let avail_horizontal_space = (available_space - padding).h - kids_sum;
126
127 let flex_kids = pass
129 .scene
130 .get_children_ids_filtered(&pass.target, |v| v.h_flex == Flex::Resize);
131 if flex_kids.len() > 0 {
133 let flex_space = Size {
135 w: avail_horizontal_space / (flex_kids.len() as i32),
136 h: pass.space.h - padding.top - padding.bottom,
137 };
138 for kid in &flex_kids {
140 pass.layout_child(kid, flex_space);
141 }
142 }
143
144 let mut max_height = 0;
146 for kid in pass.scene.get_children_ids(&pass.target) {
147 if let Some(kid) = pass.scene.get_view_mut(&kid) {
148 max_height = max_height.max(kid.bounds.size.h);
149 }
150 }
151
152 if v_flex == Intrinsic {
154 available_space.h = max_height;
155 }
156 let avail_h = available_space.h;
157 let mut x = padding.left;
158 for kid in pass.scene.get_children_ids(&pass.target) {
159 if let Some(kid) = pass.scene.get_view_mut(&kid) {
160 kid.bounds.position.x = x;
161 x += kid.bounds.size.w;
162 kid.bounds.position.y = match &kid.v_align {
163 Start => 0,
164 Center => (avail_h - kid.bounds.size.h) / 2,
165 End => (avail_h - kid.bounds.size.h),
166 } + padding.top;
167 }
168 }
169 if let Some(parent) = pass.scene.get_view_mut(pass.target) {
170 if parent.v_flex == Intrinsic {
171 parent.bounds.size.h = available_space.h + padding.top + padding.bottom;
172 }
173 if parent.h_flex == Intrinsic {
174 parent.bounds.size.w = x;
175 }
176 }
177}
178
179pub fn layout_std_panel(pass: &mut LayoutEvent) {
180 if let Some(view) = pass.scene.get_view_mut(&pass.target) {
181 if view.v_flex == Resize {
182 view.bounds.size.h = pass.space.h;
183 }
184 if view.h_flex == Resize {
185 view.bounds.size.w = pass.space.w;
186 }
187 let space = view.bounds.size.clone() - view.padding;
188 pass.layout_all_children(&pass.target.clone(), space);
189 }
190}
191
192pub fn layout_tabbed_panel(pass: &mut LayoutEvent) {
193 if let Some(view) = pass.scene.get_view_mut(&pass.target) {
194 if view.h_flex == Resize {
196 view.bounds.size.w = pass.space.w;
197 }
198 if view.v_flex == Resize {
199 view.bounds.size.h = pass.space.h;
200 }
201
202 let space = view.bounds.size.clone();
204 let tabs_id: ViewId = "tabs".into();
205 pass.layout_child(&tabs_id, space);
206
207 if let Some(view) = pass.scene.get_view(&tabs_id) {
209 let insets = Insets::new(view.bounds.size.h, 0, 0, 0);
210 for kid in &pass.scene.get_children_ids(&pass.target) {
211 if kid == &tabs_id {
212 continue;
213 }
214 pass.layout_child(kid, space - insets);
215 if let Some(view) = pass.scene.get_view_mut(kid) {
216 view.bounds.position.y = insets.top;
217 }
218 }
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use crate::LayoutEvent;
226 use crate::geom::{Bounds, Insets, Point, Size};
227 use crate::layouts::{layout_hbox, layout_std_panel, layout_tabbed_panel, layout_vbox};
228 use crate::scene::{Scene, layout_scene};
229 use crate::test::MockDrawingContext;
230 use crate::toggle_group::layout_toggle_group;
231 use crate::view::Align::{Center, End, Start};
232 use crate::view::{Align, Flex, View, ViewId};
233 use test_log::test;
234 fn layout_button(layout: &mut LayoutEvent) {
235 if let Some(view) = layout.scene.get_view_mut(&layout.target) {
236 view.bounds.size = Size::new((view.title.len() * 10) as i32, 10) + view.padding;
237 }
238 }
239 #[test]
240 fn test_button() {
241 let button = View {
242 name: "button1".into(),
243 title: "abc".into(),
244 layout: Some(layout_button),
245 padding: Insets::new_same(10),
246 ..Default::default()
247 };
248
249 let theme = MockDrawingContext::make_mock_theme();
250 let mut scene = Scene::new();
251 scene.add_view_to_parent(button, &scene.root_id());
252 layout_scene(&mut scene, &theme);
253 assert_eq!(
255 view_bounds(&scene, &"button1".into()).size,
256 Size::new(3 * 10 + 20, 10 + 20),
257 "button size is wrong"
258 );
259 }
260
261 fn view_bounds(scene: &Scene, name: &ViewId) -> Bounds {
262 if let Some(view) = scene.get_view(name) {
263 view.bounds
264 } else {
265 Bounds::new(-99, -99, -99, -99)
266 }
267 }
268
269 #[test]
270 fn test_vbox() {
271 let mut scene = Scene::new();
272 let parent_id: ViewId = "parent".into();
273 let parent_view = View {
274 name: parent_id.clone(),
275 title: "parent".into(),
276 padding: Insets::new_same(10),
277 bounds: Bounds {
278 position: Point::new(-99, -99),
279 size: Size::new(100, 100),
280 },
281 h_flex: Flex::Resize,
282 v_flex: Flex::Resize,
283 h_align: Start,
284 v_align: Start,
285 layout: Some(layout_vbox),
286 ..Default::default()
287 };
288
289 let child1_id: ViewId = "child1".into();
290 scene.add_view_to_parent(
291 View {
292 name: child1_id.clone(),
293 title: "ch1".into(),
294 h_align: Align::Start,
295 layout: Some(layout_button),
296 ..Default::default()
297 },
298 &parent_id,
299 );
300
301 let child2_id: ViewId = "child2".into();
302 scene.add_view_to_parent(
303 View {
304 name: child2_id.clone(),
305 title: "ch2".into(),
306 h_align: Align::Center,
307 layout: Some(layout_button),
308 ..Default::default()
309 },
310 &parent_id,
311 );
312
313 let child3_id: ViewId = "child3".into();
314 scene.add_view_to_parent(
315 View {
316 name: child3_id.clone(),
317 title: "ch3".into(),
318 h_align: Align::End,
319 layout: Some(layout_button),
320 ..Default::default()
321 },
322 &parent_id,
323 );
324
325 let child4_id: ViewId = "child4".into();
326 scene.add_view_to_parent(
327 View {
328 name: child4_id.clone(),
329 title: "ch4".into(),
330 h_flex: Flex::Resize,
331 v_flex: Flex::Resize,
332 layout: Some(layout_std_panel),
333 ..Default::default()
334 },
335 &parent_id,
336 );
337
338 scene.add_view_to_parent(parent_view, &scene.root_id());
339
340 let theme = MockDrawingContext::make_mock_theme();
341 layout_scene(&mut scene, &theme);
342 scene.dump();
343 if let Some(view) = scene.get_view_mut(&parent_id) {
344 assert_eq!(view.name, parent_id);
345 assert_eq!(view.bounds.position, Point::new(-99, -99));
347 assert_eq!(view.bounds.size, Size::new(200, 200));
349 if let Some(view) = scene.get_view(&child1_id) {
351 assert_eq!(view.bounds.position, Point::new(10, 10));
352 assert_eq!(view.bounds.size, Size::new(30, 10));
353 }
354 if let Some(view) = scene.get_view(&child2_id) {
356 assert_eq!(view.bounds.position, Point::new(10 + (180 - 30) / 2, 20));
357 assert_eq!(view.bounds.size, Size::new(30, 10));
358 }
359 if let Some(view) = scene.get_view(&child3_id) {
361 assert_eq!(view.bounds.position, Point::new(10 + (180 - 30), 30));
362 assert_eq!(view.bounds.size, Size::new(30, 10));
363 }
364 assert!(scene.has_view(&child4_id));
366 if let Some(view) = scene.get_view(&child4_id) {
367 assert_eq!(view.bounds.position, Point::new(10, 40));
368 assert_eq!(view.bounds.size, Size::new(180, 180 - 30));
369 }
370 }
371 }
372
373 #[test]
374 fn test_complex_tabbed_panels() {
375 let mut scene = Scene::new();
376 let tabbed_panel: ViewId = "tabbed_panel_id".into();
378 let tabs: ViewId = "tabs".into();
379 {
380 let mut tabbed_panel_view: View = View {
381 name: tabbed_panel.clone(),
382 ..Default::default()
383 };
384 tabbed_panel_view.h_flex = Flex::Resize;
385 tabbed_panel_view.v_flex = Flex::Resize;
386 tabbed_panel_view.layout = Some(layout_tabbed_panel);
387 scene.add_view_to_parent(tabbed_panel_view, &scene.root_id());
388
389 let mut tabbed_panel_tabs: View = View {
391 name: tabs.clone(),
392 ..Default::default()
393 };
394 tabbed_panel_tabs.h_flex = Flex::Resize;
395 tabbed_panel_tabs.v_flex = Flex::Intrinsic;
396 tabbed_panel_tabs.layout = Some(layout_toggle_group);
397 scene.add_view_to_parent(tabbed_panel_tabs, &tabbed_panel);
398 }
399
400 let tab1: ViewId = "tab1".into();
404 {
405 let mut view = make_standard_view(&tab1);
406 view.h_flex = Flex::Resize;
407 view.v_flex = Flex::Resize;
408 view.layout = Some(layout_hbox);
409 scene.add_view_to_parent(view, &tabbed_panel);
410
411 let b1: ViewId = "tab1_button1".into();
412 {
413 let mut button = make_standard_view(&b1);
414 button.title = "abc".into();
415 button.v_align = Start;
416 button.layout = Some(layout_button);
417 scene.add_view_to_parent(button, &tab1);
418 }
419
420 let b2: ViewId = "tab1_button2".into();
421 {
422 let mut button = make_standard_view(&b2);
423 button.v_align = Center;
424 button.layout = Some(layout_button);
425 scene.add_view_to_parent(button, &tab1);
426 }
427
428 let b3: ViewId = "tab1_button3".into();
429 {
430 let mut button = make_standard_view(&b3);
431 button.v_align = End;
432 button.layout = Some(layout_button);
433 scene.add_view_to_parent(button, &tab1);
434 }
435 }
436
437 let tab2: ViewId = "tab2".into();
440 {
441 let mut view = make_standard_view(&tab2);
442 view.h_flex = Flex::Resize;
443 view.v_flex = Flex::Resize;
444 view.layout = Some(layout_vbox);
445 scene.add_view_to_parent(view, &tabbed_panel);
446
447 let b1: ViewId = "tab2_button1".into();
448 {
449 let mut b1_view = make_standard_view(&b1);
450 b1_view.h_align = Start;
451 b1_view.h_flex = Flex::Resize;
452 b1_view.v_flex = Flex::Resize;
453 b1_view.title = "b11".into();
454 b1_view.layout = Some(layout_std_panel);
455 scene.add_view_to_parent(b1_view, &tab2);
456 }
457
458 let b2: ViewId = "tab2_button2".into();
459 {
460 let mut b2_view = make_standard_view(&b2);
461 b2_view.h_align = End;
462 b2_view.h_flex = Flex::Intrinsic;
463 b2_view.v_flex = Flex::Intrinsic;
464 b2_view.title = "b11".into();
465 b2_view.layout = Some(layout_button);
466 scene.add_view_to_parent(b2_view, &tab2);
467 }
468 }
469
470 let tab3: ViewId = "tab3".into();
472 {
473 let mut view = make_standard_view(&tab3);
474 view.h_flex = Flex::Resize;
475 view.v_flex = Flex::Resize;
476 view.layout = Some(layout_std_panel);
477 scene.add_view_to_parent(view, &tabbed_panel);
478 }
479
480 let scene_size = Size::new(200, 200);
482
483 let theme = MockDrawingContext::make_mock_theme();
484 layout_scene(&mut scene, &theme);
485 scene.dump();
486 assert_eq!(
487 scene.get_view_bounds(&scene.root_id()),
488 Some(Bounds::new(0, 0, 200, 200))
489 );
490 assert_eq!(
491 scene.get_view_bounds(&tabbed_panel),
492 Some(Bounds::new(0, 0, 200, 200))
493 );
494 assert_eq!(
495 scene.get_view_bounds(&tabs),
496 Some(Bounds::new(0, 0, 200, 20))
497 );
498 assert_eq!(
499 scene.get_view_bounds(&tab1),
500 Some(Bounds::new(0, 20, 200, 180))
501 );
502 assert_eq!(
503 scene.get_view_bounds(&tab2),
504 Some(Bounds::new(0, 20, 200, 180))
505 );
506 assert_eq!(
507 scene.get_view_bounds(&tab3),
508 Some(Bounds::new(0, 20, 200, 180))
509 );
510
511 assert_eq!(
512 scene.get_view_bounds(&"tab1_button1".into()),
513 Some(Bounds::new(0, 0, 30, 10))
514 );
515 assert_eq!(
516 scene.get_view_bounds(&"tab2_button1".into()),
517 Some(Bounds::new(0, 0, 200, 170))
518 );
519 assert_eq!(
520 scene.get_view_bounds(&"tab2_button2".into()),
521 Some(Bounds::new(170, 170, 30, 10))
522 );
523 }
524
525 fn make_standard_view(name: &ViewId) -> View {
526 View {
527 name: name.clone(),
528 ..Default::default()
529 }
530 }
531}