egui/
layout.rs

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