1use crate::{
2 emath::{pos2, vec2, Align2, NumExt, Pos2, Rect, Vec2},
3 Align,
4};
5const INFINITY: f32 = f32::INFINITY;
6
7#[derive(Clone, Copy, Debug)]
12pub(crate) struct Region {
13 pub min_rect: Rect,
21
22 pub max_rect: Rect,
34
35 pub(crate) cursor: Rect,
46}
47
48impl Region {
49 pub fn expand_to_include_rect(&mut self, rect: Rect) {
51 self.min_rect = self.min_rect.union(rect);
52 self.max_rect = self.max_rect.union(rect);
53 }
54
55 pub fn expand_to_include_x(&mut self, x: f32) {
58 self.min_rect.extend_with_x(x);
59 self.max_rect.extend_with_x(x);
60 self.cursor.extend_with_x(x);
61 }
62
63 pub fn expand_to_include_y(&mut self, y: f32) {
66 self.min_rect.extend_with_y(y);
67 self.max_rect.extend_with_y(y);
68 self.cursor.extend_with_y(y);
69 }
70
71 pub fn sanity_check(&self) {
72 debug_assert!(!self.min_rect.any_nan());
73 debug_assert!(!self.max_rect.any_nan());
74 debug_assert!(!self.cursor.any_nan());
75 }
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
83pub enum Direction {
84 LeftToRight,
85 RightToLeft,
86 TopDown,
87 BottomUp,
88}
89
90impl Direction {
91 #[inline(always)]
92 pub fn is_horizontal(self) -> bool {
93 match self {
94 Self::LeftToRight | Self::RightToLeft => true,
95 Self::TopDown | Self::BottomUp => false,
96 }
97 }
98
99 #[inline(always)]
100 pub fn is_vertical(self) -> bool {
101 match self {
102 Self::LeftToRight | Self::RightToLeft => false,
103 Self::TopDown | Self::BottomUp => true,
104 }
105 }
106}
107
108#[derive(Clone, Copy, Debug, PartialEq, Eq)]
121pub struct Layout {
123 pub main_dir: Direction,
125
126 pub main_wrap: bool,
130
131 pub main_align: Align,
133
134 pub main_justify: bool,
136
137 pub cross_align: Align,
141
142 pub cross_justify: bool,
146}
147
148impl Default for Layout {
149 fn default() -> Self {
150 Self::top_down(Align::LEFT) }
153}
154
155impl Layout {
157 #[inline(always)]
161 pub fn left_to_right(valign: Align) -> Self {
162 Self {
163 main_dir: Direction::LeftToRight,
164 main_wrap: false,
165 main_align: Align::Center, main_justify: false,
167 cross_align: valign,
168 cross_justify: false,
169 }
170 }
171
172 #[inline(always)]
176 pub fn right_to_left(valign: Align) -> Self {
177 Self {
178 main_dir: Direction::RightToLeft,
179 main_wrap: false,
180 main_align: Align::Center, main_justify: false,
182 cross_align: valign,
183 cross_justify: false,
184 }
185 }
186
187 #[inline(always)]
191 pub fn top_down(halign: Align) -> Self {
192 Self {
193 main_dir: Direction::TopDown,
194 main_wrap: false,
195 main_align: Align::Center, main_justify: false,
197 cross_align: halign,
198 cross_justify: false,
199 }
200 }
201
202 #[inline(always)]
204 pub fn top_down_justified(halign: Align) -> Self {
205 Self::top_down(halign).with_cross_justify(true)
206 }
207
208 #[inline(always)]
212 pub fn bottom_up(halign: Align) -> Self {
213 Self {
214 main_dir: Direction::BottomUp,
215 main_wrap: false,
216 main_align: Align::Center, main_justify: false,
218 cross_align: halign,
219 cross_justify: false,
220 }
221 }
222
223 #[inline(always)]
224 pub fn from_main_dir_and_cross_align(main_dir: Direction, cross_align: Align) -> Self {
225 Self {
226 main_dir,
227 main_wrap: false,
228 main_align: Align::Center, main_justify: false,
230 cross_align,
231 cross_justify: false,
232 }
233 }
234
235 #[inline(always)]
240 pub fn centered_and_justified(main_dir: Direction) -> Self {
241 Self {
242 main_dir,
243 main_wrap: false,
244 main_align: Align::Center,
245 main_justify: true,
246 cross_align: Align::Center,
247 cross_justify: true,
248 }
249 }
250
251 #[inline(always)]
256 pub fn with_main_wrap(self, main_wrap: bool) -> Self {
257 Self { main_wrap, ..self }
258 }
259
260 #[inline(always)]
262 pub fn with_main_align(self, main_align: Align) -> Self {
263 Self { main_align, ..self }
264 }
265
266 #[inline(always)]
271 pub fn with_cross_align(self, cross_align: Align) -> Self {
272 Self {
273 cross_align,
274 ..self
275 }
276 }
277
278 #[inline(always)]
282 pub fn with_main_justify(self, main_justify: bool) -> Self {
283 Self {
284 main_justify,
285 ..self
286 }
287 }
288
289 #[inline(always)]
296 pub fn with_cross_justify(self, cross_justify: bool) -> Self {
297 Self {
298 cross_justify,
299 ..self
300 }
301 }
302}
303
304impl Layout {
306 #[inline(always)]
307 pub fn main_dir(&self) -> Direction {
308 self.main_dir
309 }
310
311 #[inline(always)]
312 pub fn main_wrap(&self) -> bool {
313 self.main_wrap
314 }
315
316 #[inline(always)]
317 pub fn cross_align(&self) -> Align {
318 self.cross_align
319 }
320
321 #[inline(always)]
322 pub fn cross_justify(&self) -> bool {
323 self.cross_justify
324 }
325
326 #[inline(always)]
327 pub fn is_horizontal(&self) -> bool {
328 self.main_dir().is_horizontal()
329 }
330
331 #[inline(always)]
332 pub fn is_vertical(&self) -> bool {
333 self.main_dir().is_vertical()
334 }
335
336 pub fn prefer_right_to_left(&self) -> bool {
337 self.main_dir == Direction::RightToLeft
338 || self.main_dir.is_vertical() && self.cross_align == Align::Max
339 }
340
341 pub fn horizontal_placement(&self) -> Align {
345 match self.main_dir {
346 Direction::LeftToRight => Align::LEFT,
347 Direction::RightToLeft => Align::RIGHT,
348 Direction::TopDown | Direction::BottomUp => self.cross_align,
349 }
350 }
351
352 pub fn horizontal_align(&self) -> Align {
354 if self.is_horizontal() {
355 self.main_align
356 } else {
357 self.cross_align
358 }
359 }
360
361 pub fn vertical_align(&self) -> Align {
363 if self.is_vertical() {
364 self.main_align
365 } else {
366 self.cross_align
367 }
368 }
369
370 fn align2(&self) -> Align2 {
372 Align2([self.horizontal_align(), self.vertical_align()])
373 }
374
375 pub fn horizontal_justify(&self) -> bool {
376 if self.is_horizontal() {
377 self.main_justify
378 } else {
379 self.cross_justify
380 }
381 }
382
383 pub fn vertical_justify(&self) -> bool {
384 if self.is_vertical() {
385 self.main_justify
386 } else {
387 self.cross_justify
388 }
389 }
390}
391
392impl Layout {
394 pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect {
395 debug_assert!(size.x >= 0.0 && size.y >= 0.0);
396 debug_assert!(!outer.is_negative());
397 self.align2().align_size_within_rect(size, outer)
398 }
399
400 fn initial_cursor(&self, max_rect: Rect) -> Rect {
401 let mut cursor = max_rect;
402
403 match self.main_dir {
404 Direction::LeftToRight => {
405 cursor.max.x = INFINITY;
406 }
407 Direction::RightToLeft => {
408 cursor.min.x = -INFINITY;
409 }
410 Direction::TopDown => {
411 cursor.max.y = INFINITY;
412 }
413 Direction::BottomUp => {
414 cursor.min.y = -INFINITY;
415 }
416 }
417
418 cursor
419 }
420
421 pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region {
422 debug_assert!(!max_rect.any_nan());
423 let mut region = Region {
424 min_rect: Rect::NOTHING, max_rect,
426 cursor: self.initial_cursor(max_rect),
427 };
428 let seed = self.next_widget_position(®ion);
429 region.min_rect = Rect::from_center_size(seed, Vec2::ZERO);
430 region
431 }
432
433 pub(crate) fn available_rect_before_wrap(&self, region: &Region) -> Rect {
434 self.available_from_cursor_max_rect(region.cursor, region.max_rect)
435 }
436
437 pub(crate) fn available_size(&self, r: &Region) -> Vec2 {
440 if self.main_wrap {
441 if self.main_dir.is_horizontal() {
442 vec2(r.max_rect.width(), r.cursor.height())
443 } else {
444 vec2(r.cursor.width(), r.max_rect.height())
445 }
446 } else {
447 self.available_from_cursor_max_rect(r.cursor, r.max_rect)
448 .size()
449 }
450 }
451
452 fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect {
455 debug_assert!(!cursor.any_nan());
456 debug_assert!(!max_rect.any_nan());
457
458 let mut avail = max_rect;
464
465 match self.main_dir {
466 Direction::LeftToRight => {
467 avail.min.x = cursor.min.x;
468 avail.max.x = avail.max.x.max(cursor.min.x);
469 avail.max.x = avail.max.x.max(avail.min.x);
470 avail.max.y = avail.max.y.max(avail.min.y);
471 }
472 Direction::RightToLeft => {
473 avail.max.x = cursor.max.x;
474 avail.min.x = avail.min.x.min(cursor.max.x);
475 avail.min.x = avail.min.x.min(avail.max.x);
476 avail.max.y = avail.max.y.max(avail.min.y);
477 }
478 Direction::TopDown => {
479 avail.min.y = cursor.min.y;
480 avail.max.y = avail.max.y.max(cursor.min.y);
481 avail.max.x = avail.max.x.max(avail.min.x);
482 avail.max.y = avail.max.y.max(avail.min.y);
483 }
484 Direction::BottomUp => {
485 avail.max.y = cursor.max.y;
486 avail.min.y = avail.min.y.min(cursor.max.y);
487 avail.max.x = avail.max.x.max(avail.min.x);
488 avail.min.y = avail.min.y.min(avail.max.y);
489 }
490 }
491
492 avail = avail.intersect(cursor);
497
498 if avail.max.x < avail.min.x {
500 let x = 0.5 * (avail.min.x + avail.max.x);
501 avail.min.x = x;
502 avail.max.x = x;
503 }
504 if avail.max.y < avail.min.y {
505 let y = 0.5 * (avail.min.y + avail.max.y);
506 avail.min.y = y;
507 avail.max.y = y;
508 }
509
510 debug_assert!(!avail.any_nan());
511
512 avail
513 }
514
515 pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect {
520 region.sanity_check();
521 debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
522
523 if self.main_wrap {
524 let available_size = self.available_rect_before_wrap(region).size();
525
526 let Region {
527 mut cursor,
528 mut max_rect,
529 min_rect,
530 } = *region;
531
532 match self.main_dir {
533 Direction::LeftToRight => {
534 if available_size.x < child_size.x && max_rect.left() < cursor.left() {
535 let new_row_height = cursor.height().max(child_size.y);
537 let new_top = min_rect.bottom() + spacing.y; cursor = Rect::from_min_max(
540 pos2(max_rect.left(), new_top),
541 pos2(INFINITY, new_top + new_row_height),
542 );
543 max_rect.max.y = max_rect.max.y.max(cursor.max.y);
544 }
545 }
546 Direction::RightToLeft => {
547 if available_size.x < child_size.x && cursor.right() < max_rect.right() {
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(-INFINITY, new_top),
554 pos2(max_rect.right(), new_top + new_row_height),
555 );
556 max_rect.max.y = max_rect.max.y.max(cursor.max.y);
557 }
558 }
559 Direction::TopDown => {
560 if available_size.y < child_size.y && max_rect.top() < cursor.top() {
561 let new_col_width = cursor.width().max(child_size.x);
563 cursor = Rect::from_min_max(
564 pos2(cursor.right() + spacing.x, max_rect.top()),
565 pos2(cursor.right() + spacing.x + new_col_width, INFINITY),
566 );
567 max_rect.max.x = max_rect.max.x.max(cursor.max.x);
568 }
569 }
570 Direction::BottomUp => {
571 if available_size.y < child_size.y && cursor.bottom() < max_rect.bottom() {
572 let new_col_width = cursor.width().max(child_size.x);
574 cursor = Rect::from_min_max(
575 pos2(cursor.right() + spacing.x, -INFINITY),
576 pos2(
577 cursor.right() + spacing.x + new_col_width,
578 max_rect.bottom(),
579 ),
580 );
581 max_rect.max.x = max_rect.max.x.max(cursor.max.x);
582 }
583 }
584 }
585
586 let region = Region {
588 min_rect,
589 max_rect,
590 cursor,
591 };
592
593 self.next_frame_ignore_wrap(®ion, child_size)
594 } else {
595 self.next_frame_ignore_wrap(region, child_size)
596 }
597 }
598
599 fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect {
600 region.sanity_check();
601 debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
602
603 let available_rect = self.available_rect_before_wrap(region);
604
605 let mut frame_size = child_size;
606
607 if (self.is_vertical() && self.horizontal_align() == Align::Center)
608 || self.horizontal_justify()
609 {
610 frame_size.x = frame_size.x.max(available_rect.width()); }
612 if (self.is_horizontal() && self.vertical_align() == Align::Center)
613 || self.vertical_justify()
614 {
615 frame_size.y = frame_size.y.max(available_rect.height()); }
617
618 let align2 = match self.main_dir {
619 Direction::LeftToRight => Align2([Align::LEFT, self.vertical_align()]),
620 Direction::RightToLeft => Align2([Align::RIGHT, self.vertical_align()]),
621 Direction::TopDown => Align2([self.horizontal_align(), Align::TOP]),
622 Direction::BottomUp => Align2([self.horizontal_align(), Align::BOTTOM]),
623 };
624
625 let mut frame_rect = align2.align_size_within_rect(frame_size, available_rect);
626
627 if self.is_horizontal() && frame_rect.top() < region.cursor.top() {
628 frame_rect = frame_rect.translate(Vec2::Y * (region.cursor.top() - frame_rect.top()));
632 }
633
634 debug_assert!(!frame_rect.any_nan());
635 debug_assert!(!frame_rect.is_negative());
636
637 frame_rect
638 }
639
640 pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect {
642 debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0);
643 debug_assert!(!frame.is_negative());
644
645 if self.horizontal_justify() {
646 child_size.x = child_size.x.at_least(frame.width()); }
648 if self.vertical_justify() {
649 child_size.y = child_size.y.at_least(frame.height()); }
651 self.align_size_within_rect(child_size, frame)
652 }
653
654 pub(crate) fn next_widget_space_ignore_wrap_justify(
655 &self,
656 region: &Region,
657 size: Vec2,
658 ) -> Rect {
659 let frame = self.next_frame_ignore_wrap(region, size);
660 let rect = self.align_size_within_rect(size, frame);
661 debug_assert!(!rect.any_nan());
662 debug_assert!(!rect.is_negative());
663 rect
664 }
665
666 pub(crate) fn next_widget_position(&self, region: &Region) -> Pos2 {
668 self.next_widget_space_ignore_wrap_justify(region, Vec2::ZERO)
669 .center()
670 }
671
672 pub(crate) fn advance_cursor(&self, region: &mut Region, amount: f32) {
674 match self.main_dir {
675 Direction::LeftToRight => {
676 region.cursor.min.x += amount;
677 region.expand_to_include_x(region.cursor.min.x);
678 }
679 Direction::RightToLeft => {
680 region.cursor.max.x -= amount;
681 region.expand_to_include_x(region.cursor.max.x);
682 }
683 Direction::TopDown => {
684 region.cursor.min.y += amount;
685 region.expand_to_include_y(region.cursor.min.y);
686 }
687 Direction::BottomUp => {
688 region.cursor.max.y -= amount;
689 region.expand_to_include_y(region.cursor.max.y);
690 }
691 }
692 }
693
694 pub(crate) fn advance_after_rects(
699 &self,
700 cursor: &mut Rect,
701 frame_rect: Rect,
702 widget_rect: Rect,
703 item_spacing: Vec2,
704 ) {
705 debug_assert!(!cursor.any_nan());
706 if self.main_wrap {
707 if cursor.intersects(frame_rect.shrink(1.0)) {
708 *cursor = cursor.union(frame_rect);
710 } else {
711 match self.main_dir {
713 Direction::LeftToRight => {
714 *cursor = Rect::from_min_max(
715 pos2(f32::NAN, frame_rect.min.y),
716 pos2(INFINITY, frame_rect.max.y),
717 );
718 }
719 Direction::RightToLeft => {
720 *cursor = Rect::from_min_max(
721 pos2(-INFINITY, frame_rect.min.y),
722 pos2(f32::NAN, frame_rect.max.y),
723 );
724 }
725 Direction::TopDown => {
726 *cursor = Rect::from_min_max(
727 pos2(frame_rect.min.x, f32::NAN),
728 pos2(frame_rect.max.x, INFINITY),
729 );
730 }
731 Direction::BottomUp => {
732 *cursor = Rect::from_min_max(
733 pos2(frame_rect.min.x, -INFINITY),
734 pos2(frame_rect.max.x, f32::NAN),
735 );
736 }
737 };
738 }
739 } else {
740 if self.is_horizontal() {
742 cursor.min.y = cursor.min.y.min(frame_rect.min.y);
743 cursor.max.y = cursor.max.y.max(frame_rect.max.y);
744 } else {
745 cursor.min.x = cursor.min.x.min(frame_rect.min.x);
746 cursor.max.x = cursor.max.x.max(frame_rect.max.x);
747 }
748 }
749
750 match self.main_dir {
751 Direction::LeftToRight => {
752 cursor.min.x = widget_rect.max.x + item_spacing.x;
753 }
754 Direction::RightToLeft => {
755 cursor.max.x = widget_rect.min.x - item_spacing.x;
756 }
757 Direction::TopDown => {
758 cursor.min.y = widget_rect.max.y + item_spacing.y;
759 }
760 Direction::BottomUp => {
761 cursor.max.y = widget_rect.min.y - item_spacing.y;
762 }
763 };
764 }
765
766 pub(crate) fn end_row(&self, region: &mut Region, spacing: Vec2) {
769 if self.main_wrap {
770 match self.main_dir {
771 Direction::LeftToRight => {
772 let new_top = region.cursor.bottom() + spacing.y;
773 region.cursor = Rect::from_min_max(
774 pos2(region.max_rect.left(), new_top),
775 pos2(INFINITY, new_top + region.cursor.height()),
776 );
777 }
778 Direction::RightToLeft => {
779 let new_top = region.cursor.bottom() + spacing.y;
780 region.cursor = Rect::from_min_max(
781 pos2(-INFINITY, new_top),
782 pos2(region.max_rect.right(), new_top + region.cursor.height()),
783 );
784 }
785 Direction::TopDown | Direction::BottomUp => {}
786 }
787 }
788 }
789
790 pub(crate) fn set_row_height(&self, region: &mut Region, height: f32) {
792 if self.main_wrap && self.is_horizontal() {
793 region.cursor.max.y = region.cursor.min.y + height;
794 }
795 }
796}
797
798impl Layout {
802 #[cfg(debug_assertions)]
804 pub(crate) fn paint_text_at_cursor(
805 &self,
806 painter: &crate::Painter,
807 region: &Region,
808 stroke: epaint::Stroke,
809 text: impl ToString,
810 ) {
811 let cursor = region.cursor;
812 let next_pos = self.next_widget_position(region);
813
814 let l = 64.0;
815
816 let align = match self.main_dir {
817 Direction::LeftToRight => {
818 painter.line_segment([cursor.left_top(), cursor.left_bottom()], stroke);
819 painter.arrow(next_pos, vec2(l, 0.0), stroke);
820 Align2([Align::LEFT, self.vertical_align()])
821 }
822 Direction::RightToLeft => {
823 painter.line_segment([cursor.right_top(), cursor.right_bottom()], stroke);
824 painter.arrow(next_pos, vec2(-l, 0.0), stroke);
825 Align2([Align::RIGHT, self.vertical_align()])
826 }
827 Direction::TopDown => {
828 painter.line_segment([cursor.left_top(), cursor.right_top()], stroke);
829 painter.arrow(next_pos, vec2(0.0, l), stroke);
830 Align2([self.horizontal_align(), Align::TOP])
831 }
832 Direction::BottomUp => {
833 painter.line_segment([cursor.left_bottom(), cursor.right_bottom()], stroke);
834 painter.arrow(next_pos, vec2(0.0, -l), stroke);
835 Align2([self.horizontal_align(), Align::BOTTOM])
836 }
837 };
838
839 painter.debug_text(next_pos, align, stroke.color, text);
840 }
841}