1use emath::GuiRounding as _;
2
3use crate::{
4 emath::{pos2, vec2, Align2, NumExt, Pos2, Rect, Vec2},
5 Align,
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 = self.min_rect.union(rect);
54 self.max_rect = self.max_rect.union(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!(!self.min_rect.any_nan());
75 debug_assert!(!self.max_rect.any_nan());
76 debug_assert!(!self.cursor.any_nan());
77 }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
85pub enum Direction {
86 LeftToRight,
87 RightToLeft,
88 TopDown,
89 BottomUp,
90}
91
92impl Direction {
93 #[inline(always)]
94 pub fn is_horizontal(self) -> bool {
95 match self {
96 Self::LeftToRight | Self::RightToLeft => true,
97 Self::TopDown | Self::BottomUp => false,
98 }
99 }
100
101 #[inline(always)]
102 pub fn is_vertical(self) -> bool {
103 match self {
104 Self::LeftToRight | Self::RightToLeft => false,
105 Self::TopDown | Self::BottomUp => true,
106 }
107 }
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123pub struct Layout {
125 pub main_dir: Direction,
127
128 pub main_wrap: bool,
132
133 pub main_align: Align,
135
136 pub main_justify: bool,
138
139 pub cross_align: Align,
143
144 pub cross_justify: bool,
148}
149
150impl Default for Layout {
151 fn default() -> Self {
152 Self::top_down(Align::LEFT) }
155}
156
157impl Layout {
159 #[inline(always)]
163 pub fn left_to_right(valign: Align) -> Self {
164 Self {
165 main_dir: Direction::LeftToRight,
166 main_wrap: false,
167 main_align: Align::Center, main_justify: false,
169 cross_align: valign,
170 cross_justify: false,
171 }
172 }
173
174 #[inline(always)]
178 pub fn right_to_left(valign: Align) -> Self {
179 Self {
180 main_dir: Direction::RightToLeft,
181 main_wrap: false,
182 main_align: Align::Center, main_justify: false,
184 cross_align: valign,
185 cross_justify: false,
186 }
187 }
188
189 #[inline(always)]
193 pub fn top_down(halign: Align) -> Self {
194 Self {
195 main_dir: Direction::TopDown,
196 main_wrap: false,
197 main_align: Align::Center, main_justify: false,
199 cross_align: halign,
200 cross_justify: false,
201 }
202 }
203
204 #[inline(always)]
206 pub fn top_down_justified(halign: Align) -> Self {
207 Self::top_down(halign).with_cross_justify(true)
208 }
209
210 #[inline(always)]
214 pub fn bottom_up(halign: Align) -> Self {
215 Self {
216 main_dir: Direction::BottomUp,
217 main_wrap: false,
218 main_align: Align::Center, main_justify: false,
220 cross_align: halign,
221 cross_justify: false,
222 }
223 }
224
225 #[inline(always)]
226 pub fn from_main_dir_and_cross_align(main_dir: Direction, cross_align: Align) -> Self {
227 Self {
228 main_dir,
229 main_wrap: false,
230 main_align: Align::Center, main_justify: false,
232 cross_align,
233 cross_justify: false,
234 }
235 }
236
237 #[inline(always)]
242 pub fn centered_and_justified(main_dir: Direction) -> Self {
243 Self {
244 main_dir,
245 main_wrap: false,
246 main_align: Align::Center,
247 main_justify: true,
248 cross_align: Align::Center,
249 cross_justify: true,
250 }
251 }
252
253 #[inline(always)]
258 pub fn with_main_wrap(self, main_wrap: bool) -> Self {
259 Self { main_wrap, ..self }
260 }
261
262 #[inline(always)]
264 pub fn with_main_align(self, main_align: Align) -> Self {
265 Self { main_align, ..self }
266 }
267
268 #[inline(always)]
273 pub fn with_cross_align(self, cross_align: Align) -> Self {
274 Self {
275 cross_align,
276 ..self
277 }
278 }
279
280 #[inline(always)]
284 pub fn with_main_justify(self, main_justify: bool) -> Self {
285 Self {
286 main_justify,
287 ..self
288 }
289 }
290
291 #[inline(always)]
298 pub fn with_cross_justify(self, cross_justify: bool) -> Self {
299 Self {
300 cross_justify,
301 ..self
302 }
303 }
304}
305
306impl Layout {
308 #[inline(always)]
309 pub fn main_dir(&self) -> Direction {
310 self.main_dir
311 }
312
313 #[inline(always)]
314 pub fn main_wrap(&self) -> bool {
315 self.main_wrap
316 }
317
318 #[inline(always)]
319 pub fn cross_align(&self) -> Align {
320 self.cross_align
321 }
322
323 #[inline(always)]
324 pub fn cross_justify(&self) -> bool {
325 self.cross_justify
326 }
327
328 #[inline(always)]
329 pub fn is_horizontal(&self) -> bool {
330 self.main_dir().is_horizontal()
331 }
332
333 #[inline(always)]
334 pub fn is_vertical(&self) -> bool {
335 self.main_dir().is_vertical()
336 }
337
338 pub fn prefer_right_to_left(&self) -> bool {
339 self.main_dir == Direction::RightToLeft
340 || self.main_dir.is_vertical() && self.cross_align == Align::Max
341 }
342
343 pub fn horizontal_placement(&self) -> Align {
347 match self.main_dir {
348 Direction::LeftToRight => Align::LEFT,
349 Direction::RightToLeft => Align::RIGHT,
350 Direction::TopDown | Direction::BottomUp => self.cross_align,
351 }
352 }
353
354 pub fn horizontal_align(&self) -> Align {
356 if self.is_horizontal() {
357 self.main_align
358 } else {
359 self.cross_align
360 }
361 }
362
363 pub fn vertical_align(&self) -> Align {
365 if self.is_vertical() {
366 self.main_align
367 } else {
368 self.cross_align
369 }
370 }
371
372 fn align2(&self) -> Align2 {
374 Align2([self.horizontal_align(), self.vertical_align()])
375 }
376
377 pub fn horizontal_justify(&self) -> bool {
378 if self.is_horizontal() {
379 self.main_justify
380 } else {
381 self.cross_justify
382 }
383 }
384
385 pub fn vertical_justify(&self) -> bool {
386 if self.is_vertical() {
387 self.main_justify
388 } else {
389 self.cross_justify
390 }
391 }
392}
393
394impl Layout {
396 pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect {
397 debug_assert!(size.x >= 0.0 && size.y >= 0.0);
398 debug_assert!(!outer.is_negative());
399 self.align2().align_size_within_rect(size, outer).round_ui()
400 }
401
402 fn initial_cursor(&self, max_rect: Rect) -> Rect {
403 let mut cursor = max_rect;
404
405 match self.main_dir {
406 Direction::LeftToRight => {
407 cursor.max.x = INFINITY;
408 }
409 Direction::RightToLeft => {
410 cursor.min.x = -INFINITY;
411 }
412 Direction::TopDown => {
413 cursor.max.y = INFINITY;
414 }
415 Direction::BottomUp => {
416 cursor.min.y = -INFINITY;
417 }
418 }
419
420 cursor
421 }
422
423 pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region {
424 debug_assert!(!max_rect.any_nan());
425 let mut region = Region {
426 min_rect: Rect::NOTHING, max_rect,
428 cursor: self.initial_cursor(max_rect),
429 };
430 let seed = self.next_widget_position(®ion);
431 region.min_rect = Rect::from_center_size(seed, Vec2::ZERO);
432 region
433 }
434
435 pub(crate) fn available_rect_before_wrap(&self, region: &Region) -> Rect {
436 self.available_from_cursor_max_rect(region.cursor, region.max_rect)
437 }
438
439 pub(crate) fn available_size(&self, r: &Region) -> Vec2 {
442 if self.main_wrap {
443 if self.main_dir.is_horizontal() {
444 vec2(r.max_rect.width(), r.cursor.height())
445 } else {
446 vec2(r.cursor.width(), r.max_rect.height())
447 }
448 } else {
449 self.available_from_cursor_max_rect(r.cursor, r.max_rect)
450 .size()
451 }
452 }
453
454 fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect {
457 debug_assert!(!cursor.any_nan());
458 debug_assert!(!max_rect.any_nan());
459
460 let mut avail = max_rect;
466
467 match self.main_dir {
468 Direction::LeftToRight => {
469 avail.min.x = cursor.min.x;
470 avail.max.x = avail.max.x.max(cursor.min.x);
471 avail.max.x = avail.max.x.max(avail.min.x);
472 avail.max.y = avail.max.y.max(avail.min.y);
473 }
474 Direction::RightToLeft => {
475 avail.max.x = cursor.max.x;
476 avail.min.x = avail.min.x.min(cursor.max.x);
477 avail.min.x = avail.min.x.min(avail.max.x);
478 avail.max.y = avail.max.y.max(avail.min.y);
479 }
480 Direction::TopDown => {
481 avail.min.y = cursor.min.y;
482 avail.max.y = avail.max.y.max(cursor.min.y);
483 avail.max.x = avail.max.x.max(avail.min.x);
484 avail.max.y = avail.max.y.max(avail.min.y);
485 }
486 Direction::BottomUp => {
487 avail.max.y = cursor.max.y;
488 avail.min.y = avail.min.y.min(cursor.max.y);
489 avail.max.x = avail.max.x.max(avail.min.x);
490 avail.min.y = avail.min.y.min(avail.max.y);
491 }
492 }
493
494 avail = avail.intersect(cursor);
499
500 if avail.max.x < avail.min.x {
502 let x = 0.5 * (avail.min.x + avail.max.x);
503 avail.min.x = x;
504 avail.max.x = x;
505 }
506 if avail.max.y < avail.min.y {
507 let y = 0.5 * (avail.min.y + avail.max.y);
508 avail.min.y = y;
509 avail.max.y = y;
510 }
511
512 debug_assert!(!avail.any_nan());
513
514 avail
515 }
516
517 pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect {
522 region.sanity_check();
523 debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
524
525 if self.main_wrap {
526 let available_size = self.available_rect_before_wrap(region).size();
527
528 let Region {
529 mut cursor,
530 mut max_rect,
531 min_rect,
532 } = *region;
533
534 match self.main_dir {
535 Direction::LeftToRight => {
536 if available_size.x < child_size.x && max_rect.left() < cursor.left() {
537 let new_row_height = cursor.height().max(child_size.y);
539 let new_top = min_rect.bottom() + spacing.y; cursor = Rect::from_min_max(
542 pos2(max_rect.left(), new_top),
543 pos2(INFINITY, new_top + new_row_height),
544 );
545 max_rect.max.y = max_rect.max.y.max(cursor.max.y);
546 }
547 }
548 Direction::RightToLeft => {
549 if available_size.x < child_size.x && cursor.right() < max_rect.right() {
550 let new_row_height = cursor.height().max(child_size.y);
552 let new_top = min_rect.bottom() + spacing.y; cursor = Rect::from_min_max(
555 pos2(-INFINITY, new_top),
556 pos2(max_rect.right(), new_top + new_row_height),
557 );
558 max_rect.max.y = max_rect.max.y.max(cursor.max.y);
559 }
560 }
561 Direction::TopDown => {
562 if available_size.y < child_size.y && max_rect.top() < cursor.top() {
563 let new_col_width = cursor.width().max(child_size.x);
565 cursor = Rect::from_min_max(
566 pos2(cursor.right() + spacing.x, max_rect.top()),
567 pos2(cursor.right() + spacing.x + new_col_width, INFINITY),
568 );
569 max_rect.max.x = max_rect.max.x.max(cursor.max.x);
570 }
571 }
572 Direction::BottomUp => {
573 if available_size.y < child_size.y && cursor.bottom() < max_rect.bottom() {
574 let new_col_width = cursor.width().max(child_size.x);
576 cursor = Rect::from_min_max(
577 pos2(cursor.right() + spacing.x, -INFINITY),
578 pos2(
579 cursor.right() + spacing.x + new_col_width,
580 max_rect.bottom(),
581 ),
582 );
583 max_rect.max.x = max_rect.max.x.max(cursor.max.x);
584 }
585 }
586 }
587
588 let region = Region {
590 min_rect,
591 max_rect,
592 cursor,
593 };
594
595 self.next_frame_ignore_wrap(®ion, child_size)
596 } else {
597 self.next_frame_ignore_wrap(region, child_size)
598 }
599 }
600
601 fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect {
602 region.sanity_check();
603 debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
604
605 let available_rect = self.available_rect_before_wrap(region);
606
607 let mut frame_size = child_size;
608
609 if (self.is_vertical() && self.horizontal_align() == Align::Center)
610 || self.horizontal_justify()
611 {
612 frame_size.x = frame_size.x.max(available_rect.width()); }
614 if (self.is_horizontal() && self.vertical_align() == Align::Center)
615 || self.vertical_justify()
616 {
617 frame_size.y = frame_size.y.max(available_rect.height()); }
619
620 let align2 = match self.main_dir {
621 Direction::LeftToRight => Align2([Align::LEFT, self.vertical_align()]),
622 Direction::RightToLeft => Align2([Align::RIGHT, self.vertical_align()]),
623 Direction::TopDown => Align2([self.horizontal_align(), Align::TOP]),
624 Direction::BottomUp => Align2([self.horizontal_align(), Align::BOTTOM]),
625 };
626
627 let mut frame_rect = align2.align_size_within_rect(frame_size, available_rect);
628
629 if self.is_horizontal() && frame_rect.top() < region.cursor.top() {
630 frame_rect = frame_rect.translate(Vec2::Y * (region.cursor.top() - frame_rect.top()));
634 }
635
636 debug_assert!(!frame_rect.any_nan());
637 debug_assert!(!frame_rect.is_negative());
638
639 frame_rect.round_ui()
640 }
641
642 pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect {
644 debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
645 debug_assert!(!frame.is_negative());
646
647 if self.horizontal_justify() {
648 child_size.x = child_size.x.at_least(frame.width()); }
650 if self.vertical_justify() {
651 child_size.y = child_size.y.at_least(frame.height()); }
653 self.align_size_within_rect(child_size, frame)
654 }
655
656 pub(crate) fn next_widget_space_ignore_wrap_justify(
657 &self,
658 region: &Region,
659 size: Vec2,
660 ) -> Rect {
661 let frame = self.next_frame_ignore_wrap(region, size);
662 let rect = self.align_size_within_rect(size, frame);
663 debug_assert!(!rect.any_nan());
664 debug_assert!(!rect.is_negative());
665 rect
666 }
667
668 pub(crate) fn next_widget_position(&self, region: &Region) -> Pos2 {
670 self.next_widget_space_ignore_wrap_justify(region, Vec2::ZERO)
671 .center()
672 }
673
674 pub(crate) fn advance_cursor(&self, region: &mut Region, amount: f32) {
676 match self.main_dir {
677 Direction::LeftToRight => {
678 region.cursor.min.x += amount;
679 region.expand_to_include_x(region.cursor.min.x);
680 }
681 Direction::RightToLeft => {
682 region.cursor.max.x -= amount;
683 region.expand_to_include_x(region.cursor.max.x);
684 }
685 Direction::TopDown => {
686 region.cursor.min.y += amount;
687 region.expand_to_include_y(region.cursor.min.y);
688 }
689 Direction::BottomUp => {
690 region.cursor.max.y -= amount;
691 region.expand_to_include_y(region.cursor.max.y);
692 }
693 }
694 }
695
696 pub(crate) fn advance_after_rects(
701 &self,
702 cursor: &mut Rect,
703 frame_rect: Rect,
704 widget_rect: Rect,
705 item_spacing: Vec2,
706 ) {
707 debug_assert!(!cursor.any_nan());
708 if self.main_wrap {
709 if cursor.intersects(frame_rect.shrink(1.0)) {
710 *cursor = cursor.union(frame_rect);
712 } else {
713 match self.main_dir {
715 Direction::LeftToRight => {
716 *cursor = Rect::from_min_max(
717 pos2(f32::NAN, frame_rect.min.y),
718 pos2(INFINITY, frame_rect.max.y),
719 );
720 }
721 Direction::RightToLeft => {
722 *cursor = Rect::from_min_max(
723 pos2(-INFINITY, frame_rect.min.y),
724 pos2(f32::NAN, frame_rect.max.y),
725 );
726 }
727 Direction::TopDown => {
728 *cursor = Rect::from_min_max(
729 pos2(frame_rect.min.x, f32::NAN),
730 pos2(frame_rect.max.x, INFINITY),
731 );
732 }
733 Direction::BottomUp => {
734 *cursor = Rect::from_min_max(
735 pos2(frame_rect.min.x, -INFINITY),
736 pos2(frame_rect.max.x, f32::NAN),
737 );
738 }
739 };
740 }
741 } else {
742 if self.is_horizontal() {
744 cursor.min.y = cursor.min.y.min(frame_rect.min.y);
745 cursor.max.y = cursor.max.y.max(frame_rect.max.y);
746 } else {
747 cursor.min.x = cursor.min.x.min(frame_rect.min.x);
748 cursor.max.x = cursor.max.x.max(frame_rect.max.x);
749 }
750 }
751
752 match self.main_dir {
753 Direction::LeftToRight => {
754 cursor.min.x = widget_rect.max.x + item_spacing.x;
755 }
756 Direction::RightToLeft => {
757 cursor.max.x = widget_rect.min.x - item_spacing.x;
758 }
759 Direction::TopDown => {
760 cursor.min.y = widget_rect.max.y + item_spacing.y;
761 }
762 Direction::BottomUp => {
763 cursor.max.y = widget_rect.min.y - item_spacing.y;
764 }
765 };
766 }
767
768 pub(crate) fn end_row(&self, region: &mut Region, spacing: Vec2) {
771 if self.main_wrap {
772 match self.main_dir {
773 Direction::LeftToRight => {
774 let new_top = region.cursor.bottom() + spacing.y;
775 region.cursor = Rect::from_min_max(
776 pos2(region.max_rect.left(), new_top),
777 pos2(INFINITY, new_top + region.cursor.height()),
778 );
779 }
780 Direction::RightToLeft => {
781 let new_top = region.cursor.bottom() + spacing.y;
782 region.cursor = Rect::from_min_max(
783 pos2(-INFINITY, new_top),
784 pos2(region.max_rect.right(), new_top + region.cursor.height()),
785 );
786 }
787 Direction::TopDown | Direction::BottomUp => {}
788 }
789 }
790 }
791
792 pub(crate) fn set_row_height(&self, region: &mut Region, height: f32) {
794 if self.main_wrap && self.is_horizontal() {
795 region.cursor.max.y = region.cursor.min.y + height;
796 }
797 }
798}
799
800impl Layout {
804 #[cfg(debug_assertions)]
806 pub(crate) fn paint_text_at_cursor(
807 &self,
808 painter: &crate::Painter,
809 region: &Region,
810 stroke: epaint::Stroke,
811 text: impl ToString,
812 ) {
813 let cursor = region.cursor;
814 let next_pos = self.next_widget_position(region);
815
816 let l = 64.0;
817
818 let align = match self.main_dir {
819 Direction::LeftToRight => {
820 painter.line_segment([cursor.left_top(), cursor.left_bottom()], stroke);
821 painter.arrow(next_pos, vec2(l, 0.0), stroke);
822 Align2([Align::LEFT, self.vertical_align()])
823 }
824 Direction::RightToLeft => {
825 painter.line_segment([cursor.right_top(), cursor.right_bottom()], stroke);
826 painter.arrow(next_pos, vec2(-l, 0.0), stroke);
827 Align2([Align::RIGHT, self.vertical_align()])
828 }
829 Direction::TopDown => {
830 painter.line_segment([cursor.left_top(), cursor.right_top()], stroke);
831 painter.arrow(next_pos, vec2(0.0, l), stroke);
832 Align2([self.horizontal_align(), Align::TOP])
833 }
834 Direction::BottomUp => {
835 painter.line_segment([cursor.left_bottom(), cursor.right_bottom()], stroke);
836 painter.arrow(next_pos, vec2(0.0, -l), stroke);
837 Align2([self.horizontal_align(), Align::BOTTOM])
838 }
839 };
840
841 painter.debug_text(next_pos, align, stroke.color, text);
842 }
843}