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#[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)]
101pub struct Layout {
103 pub main_dir: Direction,
105
106 pub main_wrap: bool,
110
111 pub main_align: Align,
113
114 pub main_justify: bool,
116
117 pub cross_align: Align,
121
122 pub cross_justify: bool,
126}
127
128impl Default for Layout {
129 fn default() -> Self {
130 Self::top_down(Align::LEFT) }
133}
134
135impl Layout {
137 #[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, main_justify: false,
147 cross_align: valign,
148 cross_justify: false,
149 }
150 }
151
152 #[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, main_justify: false,
162 cross_align: valign,
163 cross_justify: false,
164 }
165 }
166
167 #[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, main_justify: false,
177 cross_align: halign,
178 cross_justify: false,
179 }
180 }
181
182 #[inline(always)]
184 pub fn top_down_justified(halign: Align) -> Self {
185 Self::top_down(halign).with_cross_justify(true)
186 }
187
188 #[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, 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, main_justify: false,
210 cross_align,
211 cross_justify: false,
212 }
213 }
214
215 #[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 #[inline(always)]
236 pub fn with_main_wrap(self, main_wrap: bool) -> Self {
237 Self { main_wrap, ..self }
238 }
239
240 #[inline(always)]
242 pub fn with_main_align(self, main_align: Align) -> Self {
243 Self { main_align, ..self }
244 }
245
246 #[inline(always)]
251 pub fn with_cross_align(self, cross_align: Align) -> Self {
252 Self {
253 cross_align,
254 ..self
255 }
256 }
257
258 #[inline(always)]
262 pub fn with_main_justify(self, main_justify: bool) -> Self {
263 Self {
264 main_justify,
265 ..self
266 }
267 }
268
269 #[inline(always)]
276 pub fn with_cross_justify(self, cross_justify: bool) -> Self {
277 Self {
278 cross_justify,
279 ..self
280 }
281 }
282}
283
284impl 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 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 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 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 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
372impl 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, max_rect,
406 cursor: self.initial_cursor(max_rect),
407 };
408 let seed = self.next_widget_position(®ion);
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 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 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 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 avail = avail.intersect(cursor);
477
478 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 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 let new_row_height = cursor.height().max(child_size.y);
520 let new_top = min_rect.bottom() + spacing.y; 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 let new_row_height = cursor.height().max(child_size.y);
533 let new_top = min_rect.bottom() + spacing.y; 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 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 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 let region = Region {
571 min_rect,
572 max_rect,
573 cursor,
574 };
575
576 self.next_frame_ignore_wrap(®ion, 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 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); }
604 if (self.is_horizontal() && self.vertical_align() == Align::Center)
605 || self.vertical_justify()
606 {
607 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); }
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 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 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()); }
649 if self.vertical_justify() {
650 child_size.y = child_size.y.at_least(frame.height()); }
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 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 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 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 *cursor |= frame_rect;
711 } else {
712 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 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 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 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
799impl Layout {
803 #[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}