From ec9f374d8c6fd6a9980798043cffb18067ec3e5c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 21 Mar 2021 10:31:18 +0100 Subject: [PATCH] Fix: centered horizontal layouts should never overflow upwards --- egui/src/layout.rs | 52 +++++++++++++++++++++++++++++++++------ emath/src/rect.rs | 11 +++++++++ epaint/src/tessellator.rs | 2 +- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/egui/src/layout.rs b/egui/src/layout.rs index c4d737ea..82203102 100644 --- a/egui/src/layout.rs +++ b/egui/src/layout.rs @@ -332,6 +332,8 @@ impl Layout { /// ## Doing layout impl Layout { pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect { + debug_assert!(size.x >= 0.0 && size.y >= 0.0); + debug_assert!(outer.is_non_negative()); self.align2().align_size_within_rect(size, outer) } @@ -393,7 +395,25 @@ impl Layout { /// Given the cursor in the region, how much space is available /// for the next widget? - fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect { + fn available_from_cursor_max_rect(&self, cursor: Rect, mut max_rect: Rect) -> Rect { + // NOTE: in normal top-down layout the cursor has moved below the current max_rect, + // but the available shouldn't be negative. + + match self.main_dir { + Direction::LeftToRight => { + max_rect.max.x = max_rect.max.x.max(cursor.min.x); + } + Direction::RightToLeft => { + max_rect.min.x = max_rect.min.x.min(cursor.max.x); + } + Direction::TopDown => { + max_rect.max.y = max_rect.max.y.max(cursor.min.y); + } + Direction::BottomUp => { + max_rect.min.y = max_rect.min.y.min(cursor.max.y); + } + } + max_rect.intersect(cursor) } @@ -402,6 +422,8 @@ impl Layout { /// This is what you then pass to `advance_after_rects`. /// Use `justify_and_align` to get the inner `widget_rect`. pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect { + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + if self.main_wrap { let available_size = self.available_rect_before_wrap(region).size(); @@ -478,6 +500,8 @@ impl Layout { } fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect { + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + let available_rect = self.available_rect_before_wrap_finite(region); let mut frame_size = child_size; @@ -485,12 +509,12 @@ impl Layout { if (self.is_vertical() && self.horizontal_align() == Align::Center) || self.horizontal_justify() { - frame_size.x = frame_size.x.at_least(available_rect.width()); // fill full width + frame_size.x = frame_size.x.max(available_rect.width()); // fill full width } if (self.is_horizontal() && self.vertical_align() == Align::Center) || self.vertical_justify() { - frame_size.y = frame_size.y.at_least(available_rect.height()); // fill full height + frame_size.y = frame_size.y.max(available_rect.height()); // fill full height } let align2 = match self.main_dir { @@ -500,11 +524,23 @@ impl Layout { Direction::BottomUp => Align2([self.horizontal_align(), Align::BOTTOM]), }; - align2.align_size_within_rect(frame_size, available_rect) + let mut frame_rect = align2.align_size_within_rect(frame_size, available_rect); + + if self.is_horizontal() && frame_rect.top() < region.cursor.top() { + // for horizontal layouts we always want to expand down, + // or we will overlap the row above. + // This is a bit hacky. Maybe we should do it for vertical layouts too. + frame_rect = frame_rect.translate(Vec2::Y * (region.cursor.top() - frame_rect.top())); + } + + frame_rect } /// Apply justify (fill width/height) and/or alignment after calling `next_space`. pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect { + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(frame.is_non_negative()); + if self.horizontal_justify() { child_size.x = child_size.x.at_least(frame.width()); // fill full width } @@ -601,16 +637,16 @@ impl Layout { match self.main_dir { Direction::LeftToRight => { - cursor.min.x = widget_rect.right() + item_spacing.x; + cursor.min.x = widget_rect.max.x + item_spacing.x; } Direction::RightToLeft => { - cursor.max.x = widget_rect.left() - item_spacing.x; + cursor.max.x = widget_rect.min.x - item_spacing.x; } Direction::TopDown => { - cursor.min.y = widget_rect.bottom() + item_spacing.y; + cursor.min.y = widget_rect.max.y + item_spacing.y; } Direction::BottomUp => { - cursor.max.y = widget_rect.top() - item_spacing.y; + cursor.max.y = widget_rect.min.y - item_spacing.y; } }; } diff --git a/emath/src/rect.rs b/emath/src/rect.rs index d8849c65..a1513a1d 100644 --- a/emath/src/rect.rs +++ b/emath/src/rect.rs @@ -287,10 +287,21 @@ impl Rect { self.max.y..=self.min.y } + #[deprecated = "Use is_negative instead"] pub fn is_empty(&self) -> bool { self.max.x < self.min.x || self.max.y < self.min.y } + /// `max.x < min.x` or `max.y < min.y`. + pub fn is_negative(&self) -> bool { + self.max.x < self.min.x || self.max.y < self.min.y + } + + /// `min.x <= max.x && min.y <= max.y`. + pub fn is_non_negative(&self) -> bool { + self.min.x <= self.max.x && self.min.y <= self.max.y + } + /// True if all members are also finite. pub fn is_finite(&self) -> bool { self.min.is_finite() && self.max.is_finite() diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index e2af6915..d57f5c9c 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -597,7 +597,7 @@ impl Tessellator { { return; } - if rect.is_empty() { + if rect.is_negative() { return; }