egui/
layout.rs

1use emath::GuiRounding as _;
2
3use crate::{
4    emath::{pos2, vec2, Align2, NumExt, Pos2, Rect, Vec2},
5    Align,
6};
7const INFINITY: f32 = f32::INFINITY;
8
9// ----------------------------------------------------------------------------
10
11/// This describes the bounds and existing contents of an [`Ui`][`crate::Ui`].
12/// It is what is used and updated by [`Layout`] when adding new widgets.
13#[derive(Clone, Copy, Debug)]
14pub(crate) struct Region {
15    /// This is the minimal size of the [`Ui`](crate::Ui).
16    /// When adding new widgets, this will generally expand.
17    ///
18    /// Always finite.
19    ///
20    /// The bounding box of all child widgets, but not necessarily a tight bounding box
21    /// since [`Ui`](crate::Ui) can start with a non-zero `min_rect` size.
22    pub min_rect: Rect,
23
24    /// The maximum size of this [`Ui`](crate::Ui). This is a *soft max*
25    /// meaning new widgets will *try* not to expand beyond it,
26    /// but if they have to, they will.
27    ///
28    /// Text will wrap at `max_rect.right()`.
29    /// Some widgets (like separator lines) will try to fill the full `max_rect` width of the ui.
30    ///
31    /// `max_rect` will always be at least the size of `min_rect`.
32    ///
33    /// If the `max_rect` size is zero, it is a signal that child widgets should be as small as possible.
34    /// If the `max_rect` size is infinite, it is a signal that child widgets should take up as much room as they want.
35    pub max_rect: Rect,
36
37    /// Where the next widget will be put.
38    ///
39    /// One side of this will always be infinite: the direction in which new widgets will be added.
40    /// The opposing side is what is incremented.
41    /// The crossing sides are initialized to `max_rect`.
42    ///
43    /// So one can think of `cursor` as a constraint on the available region.
44    ///
45    /// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child.
46    /// The cursor can thus be `style.spacing.item_spacing` pixels outside of the `min_rect`.
47    pub(crate) cursor: Rect,
48}
49
50impl Region {
51    /// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect.
52    pub fn expand_to_include_rect(&mut self, rect: Rect) {
53        self.min_rect = self.min_rect.union(rect);
54        self.max_rect = self.max_rect.union(rect);
55    }
56
57    /// Ensure we are big enough to contain the given X-coordinate.
58    /// This is sometimes useful to expand a ui to stretch to a certain place.
59    pub fn expand_to_include_x(&mut self, x: f32) {
60        self.min_rect.extend_with_x(x);
61        self.max_rect.extend_with_x(x);
62        self.cursor.extend_with_x(x);
63    }
64
65    /// Ensure we are big enough to contain the given Y-coordinate.
66    /// This is sometimes useful to expand a ui to stretch to a certain place.
67    pub fn expand_to_include_y(&mut self, y: f32) {
68        self.min_rect.extend_with_y(y);
69        self.max_rect.extend_with_y(y);
70        self.cursor.extend_with_y(y);
71    }
72
73    pub fn sanity_check(&self) {
74        debug_assert!(!self.min_rect.any_nan());
75        debug_assert!(!self.max_rect.any_nan());
76        debug_assert!(!self.cursor.any_nan());
77    }
78}
79
80// ----------------------------------------------------------------------------
81
82/// Layout direction, one of [`LeftToRight`](Direction::LeftToRight), [`RightToLeft`](Direction::RightToLeft), [`TopDown`](Direction::TopDown), [`BottomUp`](Direction::BottomUp).
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
85pub enum Direction {
86    LeftToRight,
87    RightToLeft,
88    TopDown,
89    BottomUp,
90}
91
92impl Direction {
93    #[inline(always)]
94    pub fn is_horizontal(self) -> bool {
95        match self {
96            Self::LeftToRight | Self::RightToLeft => true,
97            Self::TopDown | Self::BottomUp => false,
98        }
99    }
100
101    #[inline(always)]
102    pub fn is_vertical(self) -> bool {
103        match self {
104            Self::LeftToRight | Self::RightToLeft => false,
105            Self::TopDown | Self::BottomUp => true,
106        }
107    }
108}
109
110// ----------------------------------------------------------------------------
111
112/// The layout of a [`Ui`][`crate::Ui`], e.g. "vertical & centered".
113///
114/// ```
115/// # egui::__run_test_ui(|ui| {
116/// ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
117///     ui.label("world!");
118///     ui.label("Hello");
119/// });
120/// # });
121/// ```
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123// #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
124pub struct Layout {
125    /// Main axis direction
126    pub main_dir: Direction,
127
128    /// If true, wrap around when reading the end of the main direction.
129    /// For instance, for `main_dir == Direction::LeftToRight` this will
130    /// wrap to a new row when we reach the right side of the `max_rect`.
131    pub main_wrap: bool,
132
133    /// How to align things on the main axis.
134    pub main_align: Align,
135
136    /// Justify the main axis?
137    pub main_justify: bool,
138
139    /// How to align things on the cross axis.
140    /// For vertical layouts: put things to left, center or right?
141    /// For horizontal layouts: put things to top, center or bottom?
142    pub cross_align: Align,
143
144    /// Justify the cross axis?
145    /// For vertical layouts justify mean all widgets get maximum width.
146    /// For horizontal layouts justify mean all widgets get maximum height.
147    pub cross_justify: bool,
148}
149
150impl Default for Layout {
151    fn default() -> Self {
152        // TODO(emilk): Get from `Style` instead.
153        Self::top_down(Align::LEFT) // This is a very euro-centric default.
154    }
155}
156
157/// ## Constructors
158impl Layout {
159    /// Place elements horizontally, left to right.
160    ///
161    /// The `valign` parameter controls how to align elements vertically.
162    #[inline(always)]
163    pub fn left_to_right(valign: Align) -> Self {
164        Self {
165            main_dir: Direction::LeftToRight,
166            main_wrap: false,
167            main_align: Align::Center, // looks best to e.g. center text within a button
168            main_justify: false,
169            cross_align: valign,
170            cross_justify: false,
171        }
172    }
173
174    /// Place elements horizontally, right to left.
175    ///
176    /// The `valign` parameter controls how to align elements vertically.
177    #[inline(always)]
178    pub fn right_to_left(valign: Align) -> Self {
179        Self {
180            main_dir: Direction::RightToLeft,
181            main_wrap: false,
182            main_align: Align::Center, // looks best to e.g. center text within a button
183            main_justify: false,
184            cross_align: valign,
185            cross_justify: false,
186        }
187    }
188
189    /// Place elements vertically, top to bottom.
190    ///
191    /// Use the provided horizontal alignment.
192    #[inline(always)]
193    pub fn top_down(halign: Align) -> Self {
194        Self {
195            main_dir: Direction::TopDown,
196            main_wrap: false,
197            main_align: Align::Center, // looks best to e.g. center text within a button
198            main_justify: false,
199            cross_align: halign,
200            cross_justify: false,
201        }
202    }
203
204    /// Top-down layout justified so that buttons etc fill the full available width.
205    #[inline(always)]
206    pub fn top_down_justified(halign: Align) -> Self {
207        Self::top_down(halign).with_cross_justify(true)
208    }
209
210    /// Place elements vertically, bottom up.
211    ///
212    /// Use the provided horizontal alignment.
213    #[inline(always)]
214    pub fn bottom_up(halign: Align) -> Self {
215        Self {
216            main_dir: Direction::BottomUp,
217            main_wrap: false,
218            main_align: Align::Center, // looks best to e.g. center text within a button
219            main_justify: false,
220            cross_align: halign,
221            cross_justify: false,
222        }
223    }
224
225    #[inline(always)]
226    pub fn from_main_dir_and_cross_align(main_dir: Direction, cross_align: Align) -> Self {
227        Self {
228            main_dir,
229            main_wrap: false,
230            main_align: Align::Center, // looks best to e.g. center text within a button
231            main_justify: false,
232            cross_align,
233            cross_justify: false,
234        }
235    }
236
237    /// For when you want to add a single widget to a layout, and that widget
238    /// should use up all available space.
239    ///
240    /// Only one widget may be added to the inner `Ui`!
241    #[inline(always)]
242    pub fn centered_and_justified(main_dir: Direction) -> Self {
243        Self {
244            main_dir,
245            main_wrap: false,
246            main_align: Align::Center,
247            main_justify: true,
248            cross_align: Align::Center,
249            cross_justify: true,
250        }
251    }
252
253    /// Wrap widgets when we overflow the main axis?
254    ///
255    /// For instance, for left-to-right layouts, setting this to `true` will
256    /// put widgets on a new row if we would overflow the right side of [`crate::Ui::max_rect`].
257    #[inline(always)]
258    pub fn with_main_wrap(self, main_wrap: bool) -> Self {
259        Self { main_wrap, ..self }
260    }
261
262    /// The alignment to use on the main axis.
263    #[inline(always)]
264    pub fn with_main_align(self, main_align: Align) -> Self {
265        Self { main_align, ..self }
266    }
267
268    /// The alignment to use on the cross axis.
269    ///
270    /// The "cross" axis is the one orthogonal to the main axis.
271    /// For instance: in left-to-right layout, the main axis is horizontal and the cross axis is vertical.
272    #[inline(always)]
273    pub fn with_cross_align(self, cross_align: Align) -> Self {
274        Self {
275            cross_align,
276            ..self
277        }
278    }
279
280    /// Justify widgets on the main axis?
281    ///
282    /// Justify here means "take up all available space".
283    #[inline(always)]
284    pub fn with_main_justify(self, main_justify: bool) -> Self {
285        Self {
286            main_justify,
287            ..self
288        }
289    }
290
291    /// Justify widgets along the cross axis?
292    ///
293    /// Justify here means "take up all available space".
294    ///
295    /// The "cross" axis is the one orthogonal to the main axis.
296    /// For instance: in left-to-right layout, the main axis is horizontal and the cross axis is vertical.
297    #[inline(always)]
298    pub fn with_cross_justify(self, cross_justify: bool) -> Self {
299        Self {
300            cross_justify,
301            ..self
302        }
303    }
304}
305
306/// ## Inspectors
307impl Layout {
308    #[inline(always)]
309    pub fn main_dir(&self) -> Direction {
310        self.main_dir
311    }
312
313    #[inline(always)]
314    pub fn main_wrap(&self) -> bool {
315        self.main_wrap
316    }
317
318    #[inline(always)]
319    pub fn cross_align(&self) -> Align {
320        self.cross_align
321    }
322
323    #[inline(always)]
324    pub fn cross_justify(&self) -> bool {
325        self.cross_justify
326    }
327
328    #[inline(always)]
329    pub fn is_horizontal(&self) -> bool {
330        self.main_dir().is_horizontal()
331    }
332
333    #[inline(always)]
334    pub fn is_vertical(&self) -> bool {
335        self.main_dir().is_vertical()
336    }
337
338    pub fn prefer_right_to_left(&self) -> bool {
339        self.main_dir == Direction::RightToLeft
340            || self.main_dir.is_vertical() && self.cross_align == Align::Max
341    }
342
343    /// e.g. for adjusting the placement of something.
344    /// * in horizontal layout: left or right?
345    /// * in vertical layout: same as [`Self::horizontal_align`].
346    pub fn horizontal_placement(&self) -> Align {
347        match self.main_dir {
348            Direction::LeftToRight => Align::LEFT,
349            Direction::RightToLeft => Align::RIGHT,
350            Direction::TopDown | Direction::BottomUp => self.cross_align,
351        }
352    }
353
354    /// e.g. for when aligning text within a button.
355    pub fn horizontal_align(&self) -> Align {
356        if self.is_horizontal() {
357            self.main_align
358        } else {
359            self.cross_align
360        }
361    }
362
363    /// e.g. for when aligning text within a button.
364    pub fn vertical_align(&self) -> Align {
365        if self.is_vertical() {
366            self.main_align
367        } else {
368            self.cross_align
369        }
370    }
371
372    /// e.g. for when aligning text within a button.
373    fn align2(&self) -> Align2 {
374        Align2([self.horizontal_align(), self.vertical_align()])
375    }
376
377    pub fn horizontal_justify(&self) -> bool {
378        if self.is_horizontal() {
379            self.main_justify
380        } else {
381            self.cross_justify
382        }
383    }
384
385    pub fn vertical_justify(&self) -> bool {
386        if self.is_vertical() {
387            self.main_justify
388        } else {
389            self.cross_justify
390        }
391    }
392}
393
394/// ## Doing layout
395impl Layout {
396    pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect {
397        debug_assert!(size.x >= 0.0 && size.y >= 0.0);
398        debug_assert!(!outer.is_negative());
399        self.align2().align_size_within_rect(size, outer).round_ui()
400    }
401
402    fn initial_cursor(&self, max_rect: Rect) -> Rect {
403        let mut cursor = max_rect;
404
405        match self.main_dir {
406            Direction::LeftToRight => {
407                cursor.max.x = INFINITY;
408            }
409            Direction::RightToLeft => {
410                cursor.min.x = -INFINITY;
411            }
412            Direction::TopDown => {
413                cursor.max.y = INFINITY;
414            }
415            Direction::BottomUp => {
416                cursor.min.y = -INFINITY;
417            }
418        }
419
420        cursor
421    }
422
423    pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region {
424        debug_assert!(!max_rect.any_nan());
425        let mut region = Region {
426            min_rect: Rect::NOTHING, // temporary
427            max_rect,
428            cursor: self.initial_cursor(max_rect),
429        };
430        let seed = self.next_widget_position(&region);
431        region.min_rect = Rect::from_center_size(seed, Vec2::ZERO);
432        region
433    }
434
435    pub(crate) fn available_rect_before_wrap(&self, region: &Region) -> Rect {
436        self.available_from_cursor_max_rect(region.cursor, region.max_rect)
437    }
438
439    /// Amount of space available for a widget.
440    /// For wrapping layouts, this is the maximum (after wrap).
441    pub(crate) fn available_size(&self, r: &Region) -> Vec2 {
442        if self.main_wrap {
443            if self.main_dir.is_horizontal() {
444                vec2(r.max_rect.width(), r.cursor.height())
445            } else {
446                vec2(r.cursor.width(), r.max_rect.height())
447            }
448        } else {
449            self.available_from_cursor_max_rect(r.cursor, r.max_rect)
450                .size()
451        }
452    }
453
454    /// Given the cursor in the region, how much space is available
455    /// for the next widget?
456    fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect {
457        debug_assert!(!cursor.any_nan());
458        debug_assert!(!max_rect.any_nan());
459
460        // NOTE: in normal top-down layout the cursor has moved below the current max_rect,
461        // but the available shouldn't be negative.
462
463        // ALSO: with wrapping layouts, cursor jumps to new row before expanding max_rect.
464
465        let mut avail = max_rect;
466
467        match self.main_dir {
468            Direction::LeftToRight => {
469                avail.min.x = cursor.min.x;
470                avail.max.x = avail.max.x.max(cursor.min.x);
471                avail.max.x = avail.max.x.max(avail.min.x);
472                avail.max.y = avail.max.y.max(avail.min.y);
473            }
474            Direction::RightToLeft => {
475                avail.max.x = cursor.max.x;
476                avail.min.x = avail.min.x.min(cursor.max.x);
477                avail.min.x = avail.min.x.min(avail.max.x);
478                avail.max.y = avail.max.y.max(avail.min.y);
479            }
480            Direction::TopDown => {
481                avail.min.y = cursor.min.y;
482                avail.max.y = avail.max.y.max(cursor.min.y);
483                avail.max.x = avail.max.x.max(avail.min.x);
484                avail.max.y = avail.max.y.max(avail.min.y);
485            }
486            Direction::BottomUp => {
487                avail.max.y = cursor.max.y;
488                avail.min.y = avail.min.y.min(cursor.max.y);
489                avail.max.x = avail.max.x.max(avail.min.x);
490                avail.min.y = avail.min.y.min(avail.max.y);
491            }
492        }
493
494        // We can use the cursor to restrict the available region.
495        // For instance, we use this to restrict the available space of a parent Ui
496        // after adding a panel to it.
497        // We also use it for wrapping layouts.
498        avail = avail.intersect(cursor);
499
500        // Make sure it isn't negative:
501        if avail.max.x < avail.min.x {
502            let x = 0.5 * (avail.min.x + avail.max.x);
503            avail.min.x = x;
504            avail.max.x = x;
505        }
506        if avail.max.y < avail.min.y {
507            let y = 0.5 * (avail.min.y + avail.max.y);
508            avail.min.y = y;
509            avail.max.y = y;
510        }
511
512        debug_assert!(!avail.any_nan());
513
514        avail
515    }
516
517    /// Returns where to put the next widget that is of the given size.
518    /// The returned `frame_rect` [`Rect`] will always be justified along the cross axis.
519    /// This is what you then pass to `advance_after_rects`.
520    /// Use `justify_and_align` to get the inner `widget_rect`.
521    pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect {
522        region.sanity_check();
523        debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
524
525        if self.main_wrap {
526            let available_size = self.available_rect_before_wrap(region).size();
527
528            let Region {
529                mut cursor,
530                mut max_rect,
531                min_rect,
532            } = *region;
533
534            match self.main_dir {
535                Direction::LeftToRight => {
536                    if available_size.x < child_size.x && max_rect.left() < cursor.left() {
537                        // New row
538                        let new_row_height = cursor.height().max(child_size.y);
539                        // let new_top = cursor.bottom() + spacing.y;
540                        let new_top = min_rect.bottom() + spacing.y; // tighter packing
541                        cursor = Rect::from_min_max(
542                            pos2(max_rect.left(), new_top),
543                            pos2(INFINITY, new_top + new_row_height),
544                        );
545                        max_rect.max.y = max_rect.max.y.max(cursor.max.y);
546                    }
547                }
548                Direction::RightToLeft => {
549                    if available_size.x < child_size.x && cursor.right() < max_rect.right() {
550                        // New row
551                        let new_row_height = cursor.height().max(child_size.y);
552                        // let new_top = cursor.bottom() + spacing.y;
553                        let new_top = min_rect.bottom() + spacing.y; // tighter packing
554                        cursor = Rect::from_min_max(
555                            pos2(-INFINITY, new_top),
556                            pos2(max_rect.right(), new_top + new_row_height),
557                        );
558                        max_rect.max.y = max_rect.max.y.max(cursor.max.y);
559                    }
560                }
561                Direction::TopDown => {
562                    if available_size.y < child_size.y && max_rect.top() < cursor.top() {
563                        // New column
564                        let new_col_width = cursor.width().max(child_size.x);
565                        cursor = Rect::from_min_max(
566                            pos2(cursor.right() + spacing.x, max_rect.top()),
567                            pos2(cursor.right() + spacing.x + new_col_width, INFINITY),
568                        );
569                        max_rect.max.x = max_rect.max.x.max(cursor.max.x);
570                    }
571                }
572                Direction::BottomUp => {
573                    if available_size.y < child_size.y && cursor.bottom() < max_rect.bottom() {
574                        // New column
575                        let new_col_width = cursor.width().max(child_size.x);
576                        cursor = Rect::from_min_max(
577                            pos2(cursor.right() + spacing.x, -INFINITY),
578                            pos2(
579                                cursor.right() + spacing.x + new_col_width,
580                                max_rect.bottom(),
581                            ),
582                        );
583                        max_rect.max.x = max_rect.max.x.max(cursor.max.x);
584                    }
585                }
586            }
587
588            // Use the new cursor:
589            let region = Region {
590                min_rect,
591                max_rect,
592                cursor,
593            };
594
595            self.next_frame_ignore_wrap(&region, child_size)
596        } else {
597            self.next_frame_ignore_wrap(region, child_size)
598        }
599    }
600
601    fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect {
602        region.sanity_check();
603        debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
604
605        let available_rect = self.available_rect_before_wrap(region);
606
607        let mut frame_size = child_size;
608
609        if (self.is_vertical() && self.horizontal_align() == Align::Center)
610            || self.horizontal_justify()
611        {
612            frame_size.x = frame_size.x.max(available_rect.width()); // fill full width
613        }
614        if (self.is_horizontal() && self.vertical_align() == Align::Center)
615            || self.vertical_justify()
616        {
617            frame_size.y = frame_size.y.max(available_rect.height()); // fill full height
618        }
619
620        let align2 = match self.main_dir {
621            Direction::LeftToRight => Align2([Align::LEFT, self.vertical_align()]),
622            Direction::RightToLeft => Align2([Align::RIGHT, self.vertical_align()]),
623            Direction::TopDown => Align2([self.horizontal_align(), Align::TOP]),
624            Direction::BottomUp => Align2([self.horizontal_align(), Align::BOTTOM]),
625        };
626
627        let mut frame_rect = align2.align_size_within_rect(frame_size, available_rect);
628
629        if self.is_horizontal() && frame_rect.top() < region.cursor.top() {
630            // for horizontal layouts we always want to expand down,
631            // or we will overlap the row above.
632            // This is a bit hacky. Maybe we should do it for vertical layouts too.
633            frame_rect = frame_rect.translate(Vec2::Y * (region.cursor.top() - frame_rect.top()));
634        }
635
636        debug_assert!(!frame_rect.any_nan());
637        debug_assert!(!frame_rect.is_negative());
638
639        frame_rect.round_ui()
640    }
641
642    /// Apply justify (fill width/height) and/or alignment after calling `next_space`.
643    pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect {
644        debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
645        debug_assert!(!frame.is_negative());
646
647        if self.horizontal_justify() {
648            child_size.x = child_size.x.at_least(frame.width()); // fill full width
649        }
650        if self.vertical_justify() {
651            child_size.y = child_size.y.at_least(frame.height()); // fill full height
652        }
653        self.align_size_within_rect(child_size, frame)
654    }
655
656    pub(crate) fn next_widget_space_ignore_wrap_justify(
657        &self,
658        region: &Region,
659        size: Vec2,
660    ) -> Rect {
661        let frame = self.next_frame_ignore_wrap(region, size);
662        let rect = self.align_size_within_rect(size, frame);
663        debug_assert!(!rect.any_nan());
664        debug_assert!(!rect.is_negative());
665        rect
666    }
667
668    /// Where would the next tiny widget be centered?
669    pub(crate) fn next_widget_position(&self, region: &Region) -> Pos2 {
670        self.next_widget_space_ignore_wrap_justify(region, Vec2::ZERO)
671            .center()
672    }
673
674    /// Advance the cursor by this many points, and allocate in region.
675    pub(crate) fn advance_cursor(&self, region: &mut Region, amount: f32) {
676        match self.main_dir {
677            Direction::LeftToRight => {
678                region.cursor.min.x += amount;
679                region.expand_to_include_x(region.cursor.min.x);
680            }
681            Direction::RightToLeft => {
682                region.cursor.max.x -= amount;
683                region.expand_to_include_x(region.cursor.max.x);
684            }
685            Direction::TopDown => {
686                region.cursor.min.y += amount;
687                region.expand_to_include_y(region.cursor.min.y);
688            }
689            Direction::BottomUp => {
690                region.cursor.max.y -= amount;
691                region.expand_to_include_y(region.cursor.max.y);
692            }
693        }
694    }
695
696    /// Advance cursor after a widget was added to a specific rectangle.
697    ///
698    /// * `frame_rect`: the frame inside which a widget was e.g. centered
699    /// * `widget_rect`: the actual rect used by the widget
700    pub(crate) fn advance_after_rects(
701        &self,
702        cursor: &mut Rect,
703        frame_rect: Rect,
704        widget_rect: Rect,
705        item_spacing: Vec2,
706    ) {
707        debug_assert!(!cursor.any_nan());
708        if self.main_wrap {
709            if cursor.intersects(frame_rect.shrink(1.0)) {
710                // make row/column larger if necessary
711                *cursor = cursor.union(frame_rect);
712            } else {
713                // this is a new row or column. We temporarily use NAN for what will be filled in later.
714                match self.main_dir {
715                    Direction::LeftToRight => {
716                        *cursor = Rect::from_min_max(
717                            pos2(f32::NAN, frame_rect.min.y),
718                            pos2(INFINITY, frame_rect.max.y),
719                        );
720                    }
721                    Direction::RightToLeft => {
722                        *cursor = Rect::from_min_max(
723                            pos2(-INFINITY, frame_rect.min.y),
724                            pos2(f32::NAN, frame_rect.max.y),
725                        );
726                    }
727                    Direction::TopDown => {
728                        *cursor = Rect::from_min_max(
729                            pos2(frame_rect.min.x, f32::NAN),
730                            pos2(frame_rect.max.x, INFINITY),
731                        );
732                    }
733                    Direction::BottomUp => {
734                        *cursor = Rect::from_min_max(
735                            pos2(frame_rect.min.x, -INFINITY),
736                            pos2(frame_rect.max.x, f32::NAN),
737                        );
738                    }
739                };
740            }
741        } else {
742            // Make sure we also expand where we consider adding things (the cursor):
743            if self.is_horizontal() {
744                cursor.min.y = cursor.min.y.min(frame_rect.min.y);
745                cursor.max.y = cursor.max.y.max(frame_rect.max.y);
746            } else {
747                cursor.min.x = cursor.min.x.min(frame_rect.min.x);
748                cursor.max.x = cursor.max.x.max(frame_rect.max.x);
749            }
750        }
751
752        match self.main_dir {
753            Direction::LeftToRight => {
754                cursor.min.x = widget_rect.max.x + item_spacing.x;
755            }
756            Direction::RightToLeft => {
757                cursor.max.x = widget_rect.min.x - item_spacing.x;
758            }
759            Direction::TopDown => {
760                cursor.min.y = widget_rect.max.y + item_spacing.y;
761            }
762            Direction::BottomUp => {
763                cursor.max.y = widget_rect.min.y - item_spacing.y;
764            }
765        };
766    }
767
768    /// Move to the next row in a wrapping layout.
769    /// Otherwise does nothing.
770    pub(crate) fn end_row(&self, region: &mut Region, spacing: Vec2) {
771        if self.main_wrap {
772            match self.main_dir {
773                Direction::LeftToRight => {
774                    let new_top = region.cursor.bottom() + spacing.y;
775                    region.cursor = Rect::from_min_max(
776                        pos2(region.max_rect.left(), new_top),
777                        pos2(INFINITY, new_top + region.cursor.height()),
778                    );
779                }
780                Direction::RightToLeft => {
781                    let new_top = region.cursor.bottom() + spacing.y;
782                    region.cursor = Rect::from_min_max(
783                        pos2(-INFINITY, new_top),
784                        pos2(region.max_rect.right(), new_top + region.cursor.height()),
785                    );
786                }
787                Direction::TopDown | Direction::BottomUp => {}
788            }
789        }
790    }
791
792    /// Set row height in horizontal wrapping layout.
793    pub(crate) fn set_row_height(&self, region: &mut Region, height: f32) {
794        if self.main_wrap && self.is_horizontal() {
795            region.cursor.max.y = region.cursor.min.y + height;
796        }
797    }
798}
799
800// ----------------------------------------------------------------------------
801
802/// ## Debug stuff
803impl Layout {
804    /// Shows where the next widget is going to be placed
805    #[cfg(debug_assertions)]
806    pub(crate) fn paint_text_at_cursor(
807        &self,
808        painter: &crate::Painter,
809        region: &Region,
810        stroke: epaint::Stroke,
811        text: impl ToString,
812    ) {
813        let cursor = region.cursor;
814        let next_pos = self.next_widget_position(region);
815
816        let l = 64.0;
817
818        let align = match self.main_dir {
819            Direction::LeftToRight => {
820                painter.line_segment([cursor.left_top(), cursor.left_bottom()], stroke);
821                painter.arrow(next_pos, vec2(l, 0.0), stroke);
822                Align2([Align::LEFT, self.vertical_align()])
823            }
824            Direction::RightToLeft => {
825                painter.line_segment([cursor.right_top(), cursor.right_bottom()], stroke);
826                painter.arrow(next_pos, vec2(-l, 0.0), stroke);
827                Align2([Align::RIGHT, self.vertical_align()])
828            }
829            Direction::TopDown => {
830                painter.line_segment([cursor.left_top(), cursor.right_top()], stroke);
831                painter.arrow(next_pos, vec2(0.0, l), stroke);
832                Align2([self.horizontal_align(), Align::TOP])
833            }
834            Direction::BottomUp => {
835                painter.line_segment([cursor.left_bottom(), cursor.right_bottom()], stroke);
836                painter.arrow(next_pos, vec2(0.0, -l), stroke);
837                Align2([self.horizontal_align(), Align::BOTTOM])
838            }
839        };
840
841        painter.debug_text(next_pos, align, stroke.color, text);
842    }
843}