1use crate::{emath::*, Align};
2use std::f32::INFINITY;
3
4#[derive(Clone, Copy, Debug)]
9pub(crate) struct Region {
10 pub min_rect: Rect,
18
19 pub max_rect: Rect,
31
32 pub(crate) cursor: Rect,
43}
44
45impl Region {
46 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 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 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#[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub struct Layout {
120 pub main_dir: Direction,
122
123 pub main_wrap: bool,
127
128 pub main_align: Align,
130
131 pub main_justify: bool,
133
134 pub cross_align: Align,
138
139 pub cross_justify: bool,
143}
144
145impl Default for Layout {
146 fn default() -> Self {
147 Self::top_down(Align::LEFT) }
150}
151
152impl Layout {
154 #[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, main_justify: false,
164 cross_align: valign,
165 cross_justify: false,
166 }
167 }
168
169 #[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, main_justify: false,
179 cross_align: valign,
180 cross_justify: false,
181 }
182 }
183
184 #[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, main_justify: false,
194 cross_align: halign,
195 cross_justify: false,
196 }
197 }
198
199 #[inline(always)]
201 pub fn top_down_justified(halign: Align) -> Self {
202 Self::top_down(halign).with_cross_justify(true)
203 }
204
205 #[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, 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, main_justify: false,
227 cross_align,
228 cross_justify: false,
229 }
230 }
231
232 #[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 #[inline(always)]
253 pub fn with_main_wrap(self, main_wrap: bool) -> Self {
254 Self { main_wrap, ..self }
255 }
256
257 #[inline(always)]
259 pub fn with_main_align(self, main_align: Align) -> Self {
260 Self { main_align, ..self }
261 }
262
263 #[inline(always)]
268 pub fn with_cross_align(self, cross_align: Align) -> Self {
269 Self {
270 cross_align,
271 ..self
272 }
273 }
274
275 #[inline(always)]
279 pub fn with_main_justify(self, main_justify: bool) -> Self {
280 Self {
281 main_justify,
282 ..self
283 }
284 }
285
286 #[inline(always)]
293 pub fn with_cross_justify(self, cross_justify: bool) -> Self {
294 Self {
295 cross_justify,
296 ..self
297 }
298 }
299}
300
301impl 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 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 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 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 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
389impl 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, max_rect,
424 cursor: self.initial_cursor(max_rect),
425 };
426 let seed = self.next_widget_position(®ion);
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 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 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 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 avail = avail.intersect(cursor);
496
497 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 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 let new_row_height = cursor.height().max(child_size.y);
536 let new_top = min_rect.bottom() + spacing.y; 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 let new_row_height = cursor.height().max(child_size.y);
549 let new_top = min_rect.bottom() + spacing.y; 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 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 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 let region = Region {
587 min_rect,
588 max_rect,
589 cursor,
590 };
591
592 self.next_frame_ignore_wrap(®ion, 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()); }
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()); }
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 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 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()); }
647 if self.vertical_justify() {
648 child_size.y = child_size.y.at_least(frame.height()); }
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 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());
707 if self.main_wrap {
708 if cursor.intersects(frame_rect.shrink(1.0)) {
709 *cursor = cursor.union(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(&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 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
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}