raui_core/layout/
default_layout_engine.rs

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