egui/
layout.rs

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