Skip to main content

egui/
layout.rs

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