raui_core/layout/
default_layout_engine.rs

1use crate::{
2    layout::{CoordsMapping, Layout, LayoutEngine, LayoutItem, LayoutNode},
3    widget::{
4        unit::{
5            area::AreaBox,
6            content::ContentBox,
7            flex::FlexBox,
8            grid::GridBox,
9            image::{ImageBox, ImageBoxSizeValue},
10            size::{SizeBox, SizeBoxAspectRatio, SizeBoxSizeValue},
11            text::{TextBox, TextBoxSizeValue},
12            WidgetUnit,
13        },
14        utils::{lerp, Rect, Vec2},
15        WidgetId,
16    },
17    Scalar,
18};
19use std::collections::HashMap;
20
21#[derive(Debug, Default, Copy, Clone)]
22pub struct DefaultLayoutEngine;
23
24impl DefaultLayoutEngine {
25    pub fn layout_node(size_available: Vec2, unit: &WidgetUnit) -> Option<LayoutNode> {
26        match unit {
27            WidgetUnit::None | WidgetUnit::PortalBox(_) => None,
28            WidgetUnit::AreaBox(b) => Self::layout_area_box(size_available, b),
29            WidgetUnit::ContentBox(b) => Self::layout_content_box(size_available, b),
30            WidgetUnit::FlexBox(b) => Self::layout_flex_box(size_available, b),
31            WidgetUnit::GridBox(b) => Self::layout_grid_box(size_available, b),
32            WidgetUnit::SizeBox(b) => Self::layout_size_box(size_available, b),
33            WidgetUnit::ImageBox(b) => Self::layout_image_box(size_available, b),
34            WidgetUnit::TextBox(b) => Self::layout_text_box(size_available, b),
35        }
36    }
37
38    pub fn layout_area_box(size_available: Vec2, unit: &AreaBox) -> Option<LayoutNode> {
39        if !unit.id.is_valid() {
40            return None;
41        }
42        let (children, w, h) = if let Some(child) = Self::layout_node(size_available, &unit.slot) {
43            let w = child.local_space.width();
44            let h = child.local_space.height();
45            (vec![child], w, h)
46        } else {
47            (vec![], 0.0, 0.0)
48        };
49        let local_space = Rect {
50            left: 0.0,
51            right: w,
52            top: 0.0,
53            bottom: h,
54        };
55        Some(LayoutNode {
56            id: unit.id.to_owned(),
57            local_space,
58            children,
59        })
60    }
61
62    pub fn layout_content_box(size_available: Vec2, unit: &ContentBox) -> Option<LayoutNode> {
63        if !unit.id.is_valid() {
64            return None;
65        }
66        let children = unit
67            .items
68            .iter()
69            .filter_map(|item| {
70                let left = lerp(0.0, size_available.x, item.layout.anchors.left);
71                let left = left + item.layout.margin.left + item.layout.offset.x;
72                let right = lerp(0.0, size_available.x, item.layout.anchors.right);
73                let right = right - item.layout.margin.right + item.layout.offset.x;
74                let top = lerp(0.0, size_available.y, item.layout.anchors.top);
75                let top = top + item.layout.margin.top + item.layout.offset.y;
76                let bottom = lerp(0.0, size_available.y, item.layout.anchors.bottom);
77                let bottom = bottom - item.layout.margin.bottom + item.layout.offset.y;
78                let width = (right - left).max(0.0);
79                let height = (bottom - top).max(0.0);
80                let size = Vec2 {
81                    x: width,
82                    y: height,
83                };
84                if let Some(mut child) = Self::layout_node(size, &item.slot) {
85                    let diff = child.local_space.width() - width;
86                    let ox = lerp(0.0, diff, item.layout.align.x);
87                    child.local_space.left += left - ox;
88                    child.local_space.right += left - ox;
89                    let diff = child.local_space.height() - height;
90                    let oy = lerp(0.0, diff, item.layout.align.y);
91                    child.local_space.top += top - oy;
92                    child.local_space.bottom += top - oy;
93                    let w = child.local_space.width().min(size_available.x);
94                    let h = child.local_space.height().min(size_available.y);
95                    if item.layout.keep_in_bounds.cut.left {
96                        child.local_space.left = child.local_space.left.max(0.0);
97                        if item.layout.keep_in_bounds.preserve.width {
98                            child.local_space.right = child.local_space.left + w;
99                        }
100                    }
101                    if item.layout.keep_in_bounds.cut.right {
102                        child.local_space.right = child.local_space.right.min(size_available.x);
103                        if item.layout.keep_in_bounds.preserve.width {
104                            child.local_space.left = child.local_space.right - w;
105                        }
106                    }
107                    if item.layout.keep_in_bounds.cut.top {
108                        child.local_space.top = child.local_space.top.max(0.0);
109                        if item.layout.keep_in_bounds.preserve.height {
110                            child.local_space.bottom = child.local_space.top + h;
111                        }
112                    }
113                    if item.layout.keep_in_bounds.cut.bottom {
114                        child.local_space.bottom = child.local_space.bottom.min(size_available.y);
115                        if item.layout.keep_in_bounds.preserve.height {
116                            child.local_space.top = child.local_space.bottom - h;
117                        }
118                    }
119                    Some(child)
120                } else {
121                    None
122                }
123            })
124            .collect::<Vec<_>>();
125        Some(LayoutNode {
126            id: unit.id.to_owned(),
127            local_space: Rect {
128                left: 0.0,
129                right: size_available.x,
130                top: 0.0,
131                bottom: size_available.y,
132            },
133            children,
134        })
135    }
136
137    pub fn layout_flex_box(size_available: Vec2, unit: &FlexBox) -> Option<LayoutNode> {
138        if !unit.id.is_valid() {
139            return None;
140        }
141        if unit.wrap {
142            Some(Self::layout_flex_box_wrapping(size_available, unit))
143        } else {
144            Some(Self::layout_flex_box_no_wrap(size_available, unit))
145        }
146    }
147
148    pub fn layout_flex_box_wrapping(size_available: Vec2, unit: &FlexBox) -> LayoutNode {
149        let main_available = if unit.direction.is_horizontal() {
150            size_available.x
151        } else {
152            size_available.y
153        };
154        let (lines, count) = {
155            let mut main = 0.0;
156            let mut cross: Scalar = 0.0;
157            let mut grow = 0.0;
158            let items = unit
159                .items
160                .iter()
161                .filter(|item| item.slot.is_some() && item.slot.as_data().unwrap().id().is_valid())
162                .collect::<Vec<_>>();
163            let count = items.len();
164            let mut lines = vec![];
165            let mut line = vec![];
166            for item in items {
167                let local_main = item.layout.basis.unwrap_or_else(|| {
168                    if unit.direction.is_horizontal() {
169                        Self::calc_unit_min_width(size_available, &item.slot)
170                    } else {
171                        Self::calc_unit_min_height(size_available, &item.slot)
172                    }
173                });
174                let local_main = local_main
175                    + if unit.direction.is_horizontal() {
176                        item.layout.margin.left + item.layout.margin.right
177                    } else {
178                        item.layout.margin.top + item.layout.margin.bottom
179                    };
180                let local_cross = if unit.direction.is_horizontal() {
181                    Self::calc_unit_min_height(size_available, &item.slot)
182                } else {
183                    Self::calc_unit_min_width(size_available, &item.slot)
184                };
185                let local_cross = local_cross
186                    + if unit.direction.is_horizontal() {
187                        item.layout.margin.top + item.layout.margin.bottom
188                    } else {
189                        item.layout.margin.left + item.layout.margin.right
190                    };
191                if !line.is_empty() && main + local_main > main_available {
192                    main += line.len().saturating_sub(1) as Scalar * unit.separation;
193                    lines.push((main, cross, grow, std::mem::take(&mut line)));
194                    main = 0.0;
195                    cross = 0.0;
196                    grow = 0.0;
197                }
198                main += local_main;
199                cross = cross.max(local_cross);
200                grow += item.layout.grow;
201                line.push((item, local_main, local_cross));
202            }
203            main += line.len().saturating_sub(1) as Scalar * unit.separation;
204            lines.push((main, cross, grow, line));
205            (lines, count)
206        };
207        let mut children = Vec::with_capacity(count);
208        let mut main_max: Scalar = 0.0;
209        let mut cross_max = 0.0;
210        for (main, cross_available, grow, items) in lines {
211            let diff = main_available - main;
212            let mut new_main = 0.0;
213            let mut new_cross: Scalar = 0.0;
214            for (item, local_main, local_cross) in items {
215                let child_main = if main < main_available {
216                    local_main
217                        + if grow > 0.0 {
218                            diff * item.layout.grow / grow
219                        } else {
220                            0.0
221                        }
222                } else {
223                    local_main
224                };
225                let child_main = (child_main
226                    - if unit.direction.is_horizontal() {
227                        item.layout.margin.left + item.layout.margin.right
228                    } else {
229                        item.layout.margin.top + item.layout.margin.bottom
230                    })
231                .max(0.0);
232                let child_cross = (local_cross
233                    - if unit.direction.is_horizontal() {
234                        item.layout.margin.top + item.layout.margin.bottom
235                    } else {
236                        item.layout.margin.left + item.layout.margin.right
237                    })
238                .max(0.0);
239                let child_cross = lerp(child_cross, cross_available, item.layout.fill);
240                let rect = if unit.direction.is_horizontal() {
241                    Vec2 {
242                        x: child_main,
243                        y: child_cross,
244                    }
245                } else {
246                    Vec2 {
247                        x: child_cross,
248                        y: child_main,
249                    }
250                };
251                if let Some(mut child) = Self::layout_node(rect, &item.slot) {
252                    if unit.direction.is_horizontal() {
253                        if unit.direction.is_order_ascending() {
254                            child.local_space.left += new_main + item.layout.margin.left;
255                            child.local_space.right += new_main + item.layout.margin.left;
256                        } else {
257                            let left = child.local_space.left;
258                            let right = child.local_space.right;
259                            child.local_space.left =
260                                size_available.x - right - new_main - item.layout.margin.right;
261                            child.local_space.right =
262                                size_available.x - left - new_main - item.layout.margin.right;
263                        }
264                        new_main += rect.x + item.layout.margin.left + item.layout.margin.right;
265                        let diff = lerp(
266                            0.0,
267                            cross_available - child.local_space.height(),
268                            item.layout.align,
269                        );
270                        child.local_space.top += cross_max + item.layout.margin.top + diff;
271                        child.local_space.bottom += cross_max + item.layout.margin.top + diff;
272                        new_cross = new_cross.max(rect.y);
273                    } else {
274                        if unit.direction.is_order_ascending() {
275                            child.local_space.top += new_main + item.layout.margin.top;
276                            child.local_space.bottom += new_main + item.layout.margin.top;
277                        } else {
278                            let top = child.local_space.top;
279                            let bottom = child.local_space.bottom;
280                            child.local_space.top =
281                                size_available.y - bottom - new_main - item.layout.margin.bottom;
282                            child.local_space.bottom =
283                                size_available.y - top - new_main - item.layout.margin.bottom;
284                        }
285                        new_main += rect.y + item.layout.margin.top + item.layout.margin.bottom;
286                        let diff = lerp(
287                            0.0,
288                            cross_available - child.local_space.width(),
289                            item.layout.align,
290                        );
291                        child.local_space.left += cross_max + item.layout.margin.left + diff;
292                        child.local_space.right += cross_max + item.layout.margin.left + diff;
293                        new_cross = new_cross.max(rect.x);
294                    }
295                    new_main += unit.separation;
296                    children.push(child);
297                }
298            }
299            new_main = (new_main - unit.separation).max(0.0);
300            main_max = main_max.max(new_main);
301            cross_max += new_cross + unit.separation;
302        }
303        cross_max = (cross_max - unit.separation).max(0.0);
304        let local_space = if unit.direction.is_horizontal() {
305            Rect {
306                left: 0.0,
307                right: main_max,
308                top: 0.0,
309                bottom: cross_max,
310            }
311        } else {
312            Rect {
313                left: 0.0,
314                right: cross_max,
315                top: 0.0,
316                bottom: main_max,
317            }
318        };
319        LayoutNode {
320            id: unit.id.to_owned(),
321            local_space,
322            children,
323        }
324    }
325
326    pub fn layout_flex_box_no_wrap(size_available: Vec2, unit: &FlexBox) -> LayoutNode {
327        let (main_available, cross_available) = if unit.direction.is_horizontal() {
328            (size_available.x, size_available.y)
329        } else {
330            (size_available.y, size_available.x)
331        };
332        let mut main = 0.0;
333        let mut cross: Scalar = 0.0;
334        let mut grow = 0.0;
335        let mut shrink = 0.0;
336        let items = unit
337            .items
338            .iter()
339            .filter(|item| item.slot.is_some() && item.slot.as_data().unwrap().id().is_valid())
340            .collect::<Vec<_>>();
341        let mut axis_sizes = Vec::with_capacity(items.len());
342        for item in &items {
343            let local_main = item.layout.basis.unwrap_or_else(|| {
344                if unit.direction.is_horizontal() {
345                    Self::calc_unit_min_width(size_available, &item.slot)
346                } else {
347                    Self::calc_unit_min_height(size_available, &item.slot)
348                }
349            });
350            let local_main = local_main
351                + if unit.direction.is_horizontal() {
352                    item.layout.margin.left + item.layout.margin.right
353                } else {
354                    item.layout.margin.top + item.layout.margin.bottom
355                };
356            let local_cross = if unit.direction.is_horizontal() {
357                Self::calc_unit_min_height(size_available, &item.slot)
358            } else {
359                Self::calc_unit_min_width(size_available, &item.slot)
360            };
361            let local_cross = local_cross
362                + if unit.direction.is_horizontal() {
363                    item.layout.margin.top + item.layout.margin.bottom
364                } else {
365                    item.layout.margin.left + item.layout.margin.right
366                };
367            let local_cross = lerp(local_cross, cross_available, item.layout.fill);
368            main += local_main;
369            cross = cross.max(local_cross);
370            grow += item.layout.grow;
371            shrink += item.layout.shrink;
372            axis_sizes.push((local_main, local_cross));
373        }
374        main += items.len().saturating_sub(1) as Scalar * unit.separation;
375        let diff = main_available - main;
376        let mut new_main = 0.0;
377        let mut new_cross: Scalar = 0.0;
378        let children = items
379            .into_iter()
380            .zip(axis_sizes)
381            .filter_map(|(item, axis_size)| {
382                let child_main = if main < main_available {
383                    axis_size.0
384                        + if grow > 0.0 {
385                            diff * item.layout.grow / grow
386                        } else {
387                            0.0
388                        }
389                } else if main > main_available {
390                    axis_size.0
391                        + if shrink > 0.0 {
392                            diff * item.layout.shrink / shrink
393                        } else {
394                            0.0
395                        }
396                } else {
397                    axis_size.0
398                };
399                let child_main = (child_main
400                    - if unit.direction.is_horizontal() {
401                        item.layout.margin.left + item.layout.margin.right
402                    } else {
403                        item.layout.margin.top + item.layout.margin.bottom
404                    })
405                .max(0.0);
406                let child_cross = (axis_size.1
407                    - if unit.direction.is_horizontal() {
408                        item.layout.margin.top + item.layout.margin.bottom
409                    } else {
410                        item.layout.margin.left + item.layout.margin.right
411                    })
412                .max(0.0);
413                let rect = if unit.direction.is_horizontal() {
414                    Vec2 {
415                        x: child_main,
416                        y: child_cross,
417                    }
418                } else {
419                    Vec2 {
420                        x: child_cross,
421                        y: child_main,
422                    }
423                };
424                if let Some(mut child) = Self::layout_node(rect, &item.slot) {
425                    if unit.direction.is_horizontal() {
426                        if unit.direction.is_order_ascending() {
427                            child.local_space.left += new_main + item.layout.margin.left;
428                            child.local_space.right += new_main + item.layout.margin.left;
429                        } else {
430                            let left = child.local_space.left;
431                            let right = child.local_space.right;
432                            child.local_space.left =
433                                size_available.x - right - new_main - item.layout.margin.right;
434                            child.local_space.right =
435                                size_available.x - left - new_main - item.layout.margin.right;
436                        }
437                        new_main += rect.x + item.layout.margin.left + item.layout.margin.right;
438                        let diff = lerp(
439                            0.0,
440                            cross_available - child.local_space.height(),
441                            item.layout.align,
442                        );
443                        child.local_space.top += item.layout.margin.top + diff;
444                        child.local_space.bottom += item.layout.margin.top + diff;
445                        new_cross = new_cross.max(rect.y);
446                    } else {
447                        if unit.direction.is_order_ascending() {
448                            child.local_space.top += new_main + item.layout.margin.top;
449                            child.local_space.bottom += new_main + item.layout.margin.top;
450                        } else {
451                            let top = child.local_space.top;
452                            let bottom = child.local_space.bottom;
453                            child.local_space.top =
454                                size_available.y - bottom - new_main - item.layout.margin.bottom;
455                            child.local_space.bottom =
456                                size_available.y - top - new_main - item.layout.margin.bottom;
457                        }
458                        new_main += rect.y + item.layout.margin.top + item.layout.margin.bottom;
459                        let diff = lerp(
460                            0.0,
461                            cross_available - child.local_space.width(),
462                            item.layout.align,
463                        );
464                        child.local_space.left += item.layout.margin.left + diff;
465                        child.local_space.right += item.layout.margin.left + diff;
466                        new_cross = new_cross.max(rect.x);
467                    }
468                    new_main += unit.separation;
469                    Some(child)
470                } else {
471                    None
472                }
473            })
474            .collect::<Vec<_>>();
475        new_main = (new_main - unit.separation).max(0.0);
476        let local_space = if unit.direction.is_horizontal() {
477            Rect {
478                left: 0.0,
479                right: new_main,
480                top: 0.0,
481                bottom: new_cross,
482            }
483        } else {
484            Rect {
485                left: 0.0,
486                right: new_cross,
487                top: 0.0,
488                bottom: new_main,
489            }
490        };
491        LayoutNode {
492            id: unit.id.to_owned(),
493            local_space,
494            children,
495        }
496    }
497
498    pub fn layout_grid_box(size_available: Vec2, unit: &GridBox) -> Option<LayoutNode> {
499        if !unit.id.is_valid() {
500            return None;
501        }
502        let cell_width = if unit.cols > 0 {
503            size_available.x / unit.cols as Scalar
504        } else {
505            0.0
506        };
507        let cell_height = if unit.rows > 0 {
508            size_available.y / unit.rows as Scalar
509        } else {
510            0.0
511        };
512        let children = unit
513            .items
514            .iter()
515            .filter_map(|item| {
516                let left = item.layout.space_occupancy.left as Scalar * cell_width;
517                let right = item.layout.space_occupancy.right as Scalar * cell_width;
518                let top = item.layout.space_occupancy.top as Scalar * cell_height;
519                let bottom = item.layout.space_occupancy.bottom as Scalar * cell_height;
520                let width =
521                    (right - left - item.layout.margin.left - item.layout.margin.right).max(0.0);
522                let height =
523                    (bottom - top - item.layout.margin.top - item.layout.margin.bottom).max(0.0);
524                let size = Vec2 {
525                    x: width,
526                    y: height,
527                };
528                if let Some(mut child) = Self::layout_node(size, &item.slot) {
529                    let diff = size.x - child.local_space.width();
530                    let ox = lerp(0.0, diff, item.layout.horizontal_align);
531                    let diff = size.y - child.local_space.height();
532                    let oy = lerp(0.0, diff, item.layout.vertical_align);
533                    child.local_space.left += left + item.layout.margin.left - ox;
534                    child.local_space.right += left + item.layout.margin.left - ox;
535                    child.local_space.top += top + item.layout.margin.top - oy;
536                    child.local_space.bottom += top + item.layout.margin.top - oy;
537                    Some(child)
538                } else {
539                    None
540                }
541            })
542            .collect::<Vec<_>>();
543        Some(LayoutNode {
544            id: unit.id.to_owned(),
545            local_space: Rect {
546                left: 0.0,
547                right: size_available.x,
548                top: 0.0,
549                bottom: size_available.y,
550            },
551            children,
552        })
553    }
554
555    pub fn layout_size_box(size_available: Vec2, unit: &SizeBox) -> Option<LayoutNode> {
556        if !unit.id.is_valid() {
557            return None;
558        }
559        let mut size = Vec2 {
560            x: match unit.width {
561                SizeBoxSizeValue::Content => Self::calc_unit_min_width(size_available, &unit.slot),
562                SizeBoxSizeValue::Fill => size_available.x - unit.margin.left - unit.margin.right,
563                SizeBoxSizeValue::Exact(v) => v,
564            },
565            y: match unit.height {
566                SizeBoxSizeValue::Content => Self::calc_unit_min_height(size_available, &unit.slot),
567                SizeBoxSizeValue::Fill => size_available.y - unit.margin.top - unit.margin.bottom,
568                SizeBoxSizeValue::Exact(v) => v,
569            },
570        };
571        match unit.keep_aspect_ratio {
572            SizeBoxAspectRatio::None => {}
573            SizeBoxAspectRatio::WidthOfHeight(factor) => {
574                size.x = (size.y * factor).max(0.0);
575            }
576            SizeBoxAspectRatio::HeightOfWidth(factor) => {
577                size.y = (size.x * factor).max(0.0);
578            }
579        }
580        let children = if let Some(mut child) = Self::layout_node(size, &unit.slot) {
581            child.local_space.left += unit.margin.left;
582            child.local_space.right += unit.margin.left;
583            child.local_space.top += unit.margin.top;
584            child.local_space.bottom += unit.margin.top;
585            vec![child]
586        } else {
587            vec![]
588        };
589        let local_space = Rect {
590            left: 0.0,
591            right: size.x,
592            top: 0.0,
593            bottom: size.y,
594        };
595        Some(LayoutNode {
596            id: unit.id.to_owned(),
597            local_space,
598            children,
599        })
600    }
601
602    pub fn layout_image_box(size_available: Vec2, unit: &ImageBox) -> Option<LayoutNode> {
603        if !unit.id.is_valid() {
604            return None;
605        }
606        let local_space = Rect {
607            left: 0.0,
608            right: match unit.width {
609                ImageBoxSizeValue::Fill => size_available.x,
610                ImageBoxSizeValue::Exact(v) => v,
611            },
612            top: 0.0,
613            bottom: match unit.height {
614                ImageBoxSizeValue::Fill => size_available.y,
615                ImageBoxSizeValue::Exact(v) => v,
616            },
617        };
618        Some(LayoutNode {
619            id: unit.id.to_owned(),
620            local_space,
621            children: vec![],
622        })
623    }
624
625    pub fn layout_text_box(size_available: Vec2, unit: &TextBox) -> Option<LayoutNode> {
626        if !unit.id.is_valid() {
627            return None;
628        }
629        let local_space = Rect {
630            left: 0.0,
631            right: match unit.width {
632                TextBoxSizeValue::Fill => size_available.x,
633                TextBoxSizeValue::Exact(v) => v,
634            },
635            top: 0.0,
636            bottom: match unit.height {
637                TextBoxSizeValue::Fill => size_available.y,
638                TextBoxSizeValue::Exact(v) => v,
639            },
640        };
641        Some(LayoutNode {
642            id: unit.id.to_owned(),
643            local_space,
644            children: vec![],
645        })
646    }
647
648    fn calc_unit_min_width(size_available: Vec2, unit: &WidgetUnit) -> Scalar {
649        match unit {
650            WidgetUnit::None | WidgetUnit::PortalBox(_) => 0.0,
651            WidgetUnit::AreaBox(b) => Self::calc_unit_min_width(size_available, &b.slot),
652            WidgetUnit::ContentBox(b) => Self::calc_content_box_min_width(size_available, b),
653            WidgetUnit::FlexBox(b) => Self::calc_flex_box_min_width(size_available, b),
654            WidgetUnit::GridBox(b) => Self::calc_grid_box_min_width(size_available, b),
655            WidgetUnit::SizeBox(b) => {
656                (match b.width {
657                    SizeBoxSizeValue::Content => Self::calc_unit_min_width(size_available, &b.slot),
658                    SizeBoxSizeValue::Fill => 0.0,
659                    SizeBoxSizeValue::Exact(v) => v,
660                }) + b.margin.left
661                    + b.margin.right
662            }
663            WidgetUnit::ImageBox(b) => match b.width {
664                ImageBoxSizeValue::Fill => 0.0,
665                ImageBoxSizeValue::Exact(v) => v,
666            },
667            WidgetUnit::TextBox(b) => match b.width {
668                TextBoxSizeValue::Fill => 0.0,
669                TextBoxSizeValue::Exact(v) => v,
670            },
671        }
672    }
673
674    fn calc_content_box_min_width(size_available: Vec2, unit: &ContentBox) -> Scalar {
675        let mut result: Scalar = 0.0;
676        for item in &unit.items {
677            let size = Self::calc_unit_min_width(size_available, &item.slot)
678                + item.layout.margin.left
679                + item.layout.margin.right;
680            let width = item.layout.anchors.right - item.layout.anchors.left;
681            let size = if width > 0.0 { size / width } else { 0.0 };
682            result = result.max(size);
683        }
684        result
685    }
686
687    fn calc_flex_box_min_width(size_available: Vec2, unit: &FlexBox) -> Scalar {
688        if unit.direction.is_horizontal() {
689            Self::calc_horizontal_flex_box_min_width(size_available, unit)
690        } else {
691            Self::calc_vertical_flex_box_min_width(size_available, unit)
692        }
693    }
694
695    fn calc_horizontal_flex_box_min_width(size_available: Vec2, unit: &FlexBox) -> Scalar {
696        if unit.wrap {
697            let mut result: Scalar = 0.0;
698            let mut line = 0.0;
699            let mut first = true;
700            for item in &unit.items {
701                let size = Self::calc_unit_min_width(size_available, &item.slot)
702                    + item.layout.margin.left
703                    + item.layout.margin.right;
704                if first || line + size <= size_available.x {
705                    line += size;
706                    if !first {
707                        line += unit.separation;
708                    }
709                    first = false;
710                } else {
711                    result = result.max(line);
712                    line = 0.0;
713                    first = true;
714                }
715            }
716            result.max(line)
717        } else {
718            let mut result = 0.0;
719            for item in &unit.items {
720                result += Self::calc_unit_min_width(size_available, &item.slot)
721                    + item.layout.margin.left
722                    + item.layout.margin.right;
723            }
724            result + (unit.items.len().saturating_sub(1) as Scalar) * unit.separation
725        }
726    }
727
728    fn calc_vertical_flex_box_min_width(size_available: Vec2, unit: &FlexBox) -> Scalar {
729        if unit.wrap {
730            let mut result = 0.0;
731            let mut line_length = 0.0;
732            let mut line: Scalar = 0.0;
733            let mut lines: usize = 0;
734            let mut first = true;
735            for item in &unit.items {
736                let width = Self::calc_unit_min_width(size_available, &item.slot)
737                    + item.layout.margin.left
738                    + item.layout.margin.right;
739                let height = Self::calc_unit_min_height(size_available, &item.slot)
740                    + item.layout.margin.top
741                    + item.layout.margin.bottom;
742                if first || line_length + height <= size_available.y {
743                    line_length += height;
744                    if !first {
745                        line_length += unit.separation;
746                    }
747                    line = line.max(width);
748                    first = false;
749                } else {
750                    result += line;
751                    line_length = 0.0;
752                    line = 0.0;
753                    lines += 1;
754                    first = true;
755                }
756            }
757            result += line;
758            lines += 1;
759            result + (lines.saturating_sub(1) as Scalar) * unit.separation
760        } else {
761            unit.items.iter().fold(0.0, |a, item| {
762                (Self::calc_unit_min_width(size_available, &item.slot)
763                    + item.layout.margin.left
764                    + item.layout.margin.right)
765                    .max(a)
766            })
767        }
768    }
769
770    fn calc_grid_box_min_width(size_available: Vec2, unit: &GridBox) -> Scalar {
771        let mut result: Scalar = 0.0;
772        for item in &unit.items {
773            let size = Self::calc_unit_min_width(size_available, &item.slot)
774                + item.layout.margin.left
775                + item.layout.margin.right;
776            let size = if size > 0.0 {
777                (item.layout.space_occupancy.width() as Scalar * size) / unit.cols as Scalar
778            } else {
779                0.0
780            };
781            result = result.max(size);
782        }
783        result
784    }
785
786    fn calc_unit_min_height(size_available: Vec2, unit: &WidgetUnit) -> Scalar {
787        match unit {
788            WidgetUnit::None | WidgetUnit::PortalBox(_) => 0.0,
789            WidgetUnit::AreaBox(b) => Self::calc_unit_min_height(size_available, &b.slot),
790            WidgetUnit::ContentBox(b) => Self::calc_content_box_min_height(size_available, b),
791            WidgetUnit::FlexBox(b) => Self::calc_flex_box_min_height(size_available, b),
792            WidgetUnit::GridBox(b) => Self::calc_grid_box_min_height(size_available, b),
793            WidgetUnit::SizeBox(b) => {
794                (match b.height {
795                    SizeBoxSizeValue::Content => {
796                        Self::calc_unit_min_height(size_available, &b.slot)
797                    }
798                    SizeBoxSizeValue::Fill => 0.0,
799                    SizeBoxSizeValue::Exact(v) => v,
800                }) + b.margin.top
801                    + b.margin.bottom
802            }
803            WidgetUnit::ImageBox(b) => match b.height {
804                ImageBoxSizeValue::Fill => 0.0,
805                ImageBoxSizeValue::Exact(v) => v,
806            },
807            WidgetUnit::TextBox(b) => match b.height {
808                TextBoxSizeValue::Fill => 0.0,
809                TextBoxSizeValue::Exact(v) => v,
810            },
811        }
812    }
813
814    fn calc_content_box_min_height(size_available: Vec2, unit: &ContentBox) -> Scalar {
815        let mut result: Scalar = 0.0;
816        for item in &unit.items {
817            let size = Self::calc_unit_min_height(size_available, &item.slot)
818                + item.layout.margin.top
819                + item.layout.margin.bottom;
820            let height = item.layout.anchors.bottom - item.layout.anchors.top;
821            let size = if height > 0.0 { size / height } else { 0.0 };
822            result = result.max(size);
823        }
824        result
825    }
826
827    fn calc_flex_box_min_height(size_available: Vec2, unit: &FlexBox) -> Scalar {
828        if unit.direction.is_horizontal() {
829            Self::calc_horizontal_flex_box_min_height(size_available, unit)
830        } else {
831            Self::calc_vertical_flex_box_min_height(size_available, unit)
832        }
833    }
834
835    fn calc_horizontal_flex_box_min_height(size_available: Vec2, unit: &FlexBox) -> Scalar {
836        if unit.wrap {
837            let mut result = 0.0;
838            let mut line_length = 0.0;
839            let mut line: Scalar = 0.0;
840            let mut lines: usize = 0;
841            let mut first = true;
842            for item in &unit.items {
843                let width = Self::calc_unit_min_width(size_available, &item.slot)
844                    + item.layout.margin.left
845                    + item.layout.margin.right;
846                let height = Self::calc_unit_min_height(size_available, &item.slot)
847                    + item.layout.margin.top
848                    + item.layout.margin.bottom;
849                if first || line_length + width <= size_available.x {
850                    line_length += width;
851                    if !first {
852                        line_length += unit.separation;
853                    }
854                    line = line.max(height);
855                    first = false;
856                } else {
857                    result += line;
858                    line_length = 0.0;
859                    line = 0.0;
860                    lines += 1;
861                    first = true;
862                }
863            }
864            result += line;
865            lines += 1;
866            result + (lines.saturating_sub(1) as Scalar) * unit.separation
867        } else {
868            unit.items.iter().fold(0.0, |a, item| {
869                (Self::calc_unit_min_height(size_available, &item.slot)
870                    + item.layout.margin.top
871                    + item.layout.margin.bottom)
872                    .max(a)
873            })
874        }
875    }
876
877    fn calc_vertical_flex_box_min_height(size_available: Vec2, unit: &FlexBox) -> Scalar {
878        if unit.wrap {
879            let mut result: Scalar = 0.0;
880            let mut line = 0.0;
881            let mut first = true;
882            for item in &unit.items {
883                let size = Self::calc_unit_min_height(size_available, &item.slot)
884                    + item.layout.margin.top
885                    + item.layout.margin.bottom;
886                if first || line + size <= size_available.y {
887                    line += size;
888                    if !first {
889                        line += unit.separation;
890                    }
891                    first = false;
892                } else {
893                    result = result.max(line);
894                    line = 0.0;
895                    first = true;
896                }
897            }
898            result.max(line)
899        } else {
900            let mut result = 0.0;
901            for item in &unit.items {
902                result += Self::calc_unit_min_height(size_available, &item.slot)
903                    + item.layout.margin.top
904                    + item.layout.margin.bottom;
905            }
906            result + (unit.items.len().saturating_sub(1) as Scalar) * unit.separation
907        }
908    }
909
910    fn calc_grid_box_min_height(size_available: Vec2, unit: &GridBox) -> Scalar {
911        let mut result: Scalar = 0.0;
912        for item in &unit.items {
913            let size = Self::calc_unit_min_height(size_available, &item.slot)
914                + item.layout.margin.top
915                + item.layout.margin.bottom;
916            let size = if size > 0.0 {
917                (item.layout.space_occupancy.height() as Scalar * size) / unit.cols as Scalar
918            } else {
919                0.0
920            };
921            result = result.max(size);
922        }
923        result
924    }
925
926    fn unpack_node(
927        parent: Option<&WidgetId>,
928        ui_space: Rect,
929        node: LayoutNode,
930        items: &mut HashMap<WidgetId, LayoutItem>,
931    ) {
932        let LayoutNode {
933            id,
934            local_space,
935            children,
936        } = node;
937        let ui_space = Rect {
938            left: local_space.left + ui_space.left,
939            right: local_space.right + ui_space.left,
940            top: local_space.top + ui_space.top,
941            bottom: local_space.bottom + ui_space.top,
942        };
943        for node in children {
944            Self::unpack_node(Some(&id), ui_space, node, items);
945        }
946        items.insert(
947            id,
948            LayoutItem {
949                local_space,
950                ui_space,
951                parent: parent.cloned(),
952            },
953        );
954    }
955}
956
957impl LayoutEngine<()> for DefaultLayoutEngine {
958    fn layout(&mut self, mapping: &CoordsMapping, tree: &WidgetUnit) -> Result<Layout, ()> {
959        let ui_space = mapping.virtual_area();
960        if let Some(root) = Self::layout_node(ui_space.size(), tree) {
961            let mut items = HashMap::with_capacity(root.count());
962            Self::unpack_node(None, ui_space, root, &mut items);
963            Ok(Layout { ui_space, items })
964        } else {
965            Ok(Layout {
966                ui_space,
967                items: Default::default(),
968            })
969        }
970    }
971}