egui/
layout.rs

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