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#[derive(Clone, Copy, Debug)]
14pub(crate) struct Region {
15 pub min_rect: Rect,
23
24 pub max_rect: Rect,
36
37 pub(crate) cursor: Rect,
48}
49
50impl Region {
51 pub fn expand_to_include_rect(&mut self, rect: Rect) {
53 self.min_rect |= rect;
54 self.max_rect |= rect;
55 }
56
57 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 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#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131pub struct Layout {
133 pub main_dir: Direction,
135
136 pub main_wrap: bool,
140
141 pub main_align: Align,
143
144 pub main_justify: bool,
146
147 pub cross_align: Align,
151
152 pub cross_justify: bool,
156}
157
158impl Default for Layout {
159 fn default() -> Self {
160 Self::top_down(Align::LEFT) }
163}
164
165impl Layout {
167 #[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, main_justify: false,
177 cross_align: valign,
178 cross_justify: false,
179 }
180 }
181
182 #[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, main_justify: false,
192 cross_align: valign,
193 cross_justify: false,
194 }
195 }
196
197 #[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, main_justify: false,
207 cross_align: halign,
208 cross_justify: false,
209 }
210 }
211
212 #[inline(always)]
214 pub fn top_down_justified(halign: Align) -> Self {
215 Self::top_down(halign).with_cross_justify(true)
216 }
217
218 #[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, 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, main_justify: false,
240 cross_align,
241 cross_justify: false,
242 }
243 }
244
245 #[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 #[inline(always)]
266 pub fn with_main_wrap(self, main_wrap: bool) -> Self {
267 Self { main_wrap, ..self }
268 }
269
270 #[inline(always)]
272 pub fn with_main_align(self, main_align: Align) -> Self {
273 Self { main_align, ..self }
274 }
275
276 #[inline(always)]
281 pub fn with_cross_align(self, cross_align: Align) -> Self {
282 Self {
283 cross_align,
284 ..self
285 }
286 }
287
288 #[inline(always)]
292 pub fn with_main_justify(self, main_justify: bool) -> Self {
293 Self {
294 main_justify,
295 ..self
296 }
297 }
298
299 #[inline(always)]
306 pub fn with_cross_justify(self, cross_justify: bool) -> Self {
307 Self {
308 cross_justify,
309 ..self
310 }
311 }
312}
313
314impl 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 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 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 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 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
402impl 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, max_rect,
436 cursor: self.initial_cursor(max_rect),
437 };
438 let seed = self.next_widget_position(®ion);
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 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 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 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 avail = avail.intersect(cursor);
507
508 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 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 let new_row_height = cursor.height().max(child_size.y);
550 let new_top = min_rect.bottom() + spacing.y; 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 let new_row_height = cursor.height().max(child_size.y);
563 let new_top = min_rect.bottom() + spacing.y; 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 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 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 let region = Region {
601 min_rect,
602 max_rect,
603 cursor,
604 };
605
606 self.next_frame_ignore_wrap(®ion, 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()); }
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()); }
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 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 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()); }
667 if self.vertical_justify() {
668 child_size.y = child_size.y.at_least(frame.height()); }
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 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 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 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 *cursor |= frame_rect;
729 } else {
730 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 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 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 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
817impl Layout {
821 #[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}