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