From a93c6cd5d25ef966dca805073eb6f2e33f3075a2 Mon Sep 17 00:00:00 2001 From: Varphone Wong Date: Fri, 8 Mar 2024 17:32:23 +0800 Subject: [PATCH] `egui`: Fixed the incorrect display of the Window frame with a wide border or large rounding (#4032) Currently, the Window frame is displayed incorrectly when using a wide border or large rounding. * Closes #3806 * Closes #4024 * Closes #4025 * Screencast of egui demo app (emilk:master) [window-frame-bug.webm](https://github.com/emilk/egui/assets/1274171/391f67fa-ae6f-445a-8c64-1bb575770127) * Screencast of egui demo app (varphone:hotfix/window-custom-frame) [window-frame-fixed.webm](https://github.com/emilk/egui/assets/1274171/1953124e-9f7a-4c2d-9024-5d2eece6b87c) --- crates/egui/src/containers/resize.rs | 9 +- crates/egui/src/containers/window.rs | 76 +++++++++++----- crates/egui/src/painter.rs | 6 ++ crates/egui/src/style.rs | 110 ++++++++++++++++++++++++ crates/epaint/src/shape.rs | 124 +++++++++++++++++++++++++++ 5 files changed, 300 insertions(+), 25 deletions(-) diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 22286368b7c6..ef0d045de6fd 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -369,19 +369,22 @@ use epaint::Stroke; pub fn paint_resize_corner(ui: &Ui, response: &Response) { let stroke = ui.style().interact(response).fg_stroke; - paint_resize_corner_with_style(ui, &response.rect, stroke, Align2::RIGHT_BOTTOM); + paint_resize_corner_with_style(ui, &response.rect, stroke.color, Align2::RIGHT_BOTTOM); } pub fn paint_resize_corner_with_style( ui: &Ui, rect: &Rect, - stroke: impl Into, + color: impl Into, corner: Align2, ) { let painter = ui.painter(); let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect)); let mut w = 2.0; - let stroke = stroke.into(); + let stroke = Stroke { + width: 1.0, // Set width to 1.0 to prevent overlapping + color: color.into(), + }; while w <= rect.width() && w <= rect.height() { painter.line_segment( diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 8cf5475ebbde..eaea0f5ac2ba 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -392,7 +392,12 @@ impl<'open> Window<'open> { let header_color = frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill); - let window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); + let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); + // Keep the original inner margin for later use + let window_margin = window_frame.inner_margin; + let border_padding = window_frame.stroke.width / 2.0; + // Add border padding to the inner margin to prevent it from covering the contents + window_frame.inner_margin += border_padding; let is_explicitly_closed = matches!(open, Some(false)); let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); @@ -420,9 +425,10 @@ impl<'open> Window<'open> { // Calculate roughly how much larger the window size is compared to the inner rect let (title_bar_height, title_content_spacing) = if with_title_bar { let style = ctx.style(); - let window_margin = window_frame.inner_margin; let spacing = window_margin.top + window_margin.bottom; let height = ctx.fonts(|f| title.font_height(f, &style)) + spacing; + window_frame.rounding.ne = window_frame.rounding.ne.clamp(0.0, height / 2.0); + window_frame.rounding.nw = window_frame.rounding.nw.clamp(0.0, height / 2.0); (height, spacing) } else { (0.0, 0.0) @@ -495,21 +501,33 @@ impl<'open> Window<'open> { .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response))); let outer_rect = frame.end(&mut area_content_ui).rect; - paint_resize_corner(&area_content_ui, &possible, outer_rect, frame_stroke); + paint_resize_corner( + &area_content_ui, + &possible, + outer_rect, + frame_stroke, + window_frame.rounding, + ); // END FRAME -------------------------------- if let Some(title_bar) = title_bar { - if on_top && area_content_ui.visuals().window_highlight_topmost { - let rect = Rect::from_min_size( - outer_rect.min, - Vec2 { - x: outer_rect.size().x, - y: title_bar_height, - }, - ); + let mut title_rect = Rect::from_min_size( + outer_rect.min + vec2(border_padding, border_padding), + Vec2 { + x: outer_rect.size().x - border_padding * 2.0, + y: title_bar_height, + }, + ); + title_rect = area_content_ui.painter().round_rect_to_pixels(title_rect); + + if on_top && area_content_ui.visuals().window_highlight_topmost { let mut round = window_frame.rounding; + + // Eliminate the rounding gap between the title bar and the window frame + round -= border_padding; + if !is_collapsed { round.se = 0.0; round.sw = 0.0; @@ -517,18 +535,18 @@ impl<'open> Window<'open> { area_content_ui.painter().set( *where_to_put_header_background, - RectShape::filled(rect, round, header_color), + RectShape::filled(title_rect, round, header_color), ); }; // Fix title bar separator line position if let Some(response) = &mut content_response { - response.rect.min.y = outer_rect.min.y + title_bar_height; + response.rect.min.y = outer_rect.min.y + title_bar_height + border_padding; } title_bar.ui( &mut area_content_ui, - outer_rect, + title_rect, &content_response, open, &mut collapsing, @@ -558,23 +576,34 @@ fn paint_resize_corner( possible: &PossibleInteractions, outer_rect: Rect, stroke: impl Into, + rounding: impl Into, ) { - let corner = if possible.resize_right && possible.resize_bottom { - Align2::RIGHT_BOTTOM + let stroke = stroke.into(); + let rounding = rounding.into(); + let (corner, radius) = if possible.resize_right && possible.resize_bottom { + (Align2::RIGHT_BOTTOM, rounding.se) } else if possible.resize_left && possible.resize_bottom { - Align2::LEFT_BOTTOM + (Align2::LEFT_BOTTOM, rounding.sw) } else if possible.resize_left && possible.resize_top { - Align2::LEFT_TOP + (Align2::LEFT_TOP, rounding.nw) } else if possible.resize_right && possible.resize_top { - Align2::RIGHT_TOP + (Align2::RIGHT_TOP, rounding.ne) } else { return; }; + // Adjust the corner offset to accommodate the stroke width and window rounding + let offset = if radius <= 2.0 && stroke.width < 2.0 { + 2.0 + } else { + // The corner offset is calculated to make the corner appear to be in the correct position + (2.0_f32.sqrt() * (1.0 + radius + stroke.width / 2.0) - radius) + * 45.0_f32.to_radians().cos() + }; let corner_size = Vec2::splat(ui.visuals().resize_corner_size); let corner_rect = corner.align_size_within_rect(corner_size, outer_rect); - let corner_rect = corner_rect.translate(-2.0 * corner.to_sign()); // move away from corner - crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke, corner); + let corner_rect = corner_rect.translate(-offset * corner.to_sign()); // move away from corner + crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke.color, corner); } // ---------------------------------------------------------------------------- @@ -1036,7 +1065,10 @@ impl TitleBar { let y = content_response.rect.top(); // let y = lerp(self.rect.bottom()..=content_response.rect.top(), 0.5); let stroke = ui.visuals().widgets.noninteractive.bg_stroke; - ui.painter().hline(outer_rect.x_range(), y, stroke); + // Workaround: To prevent border infringement, + // the 0.1 value should ideally be calculated using TessellationOptions::feathering_size_in_pixels + let x_range = outer_rect.x_range().shrink(0.1); + ui.painter().hline(x_range, y, stroke); } // Don't cover the close- and collapse buttons: diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 8689ec815306..788319dc9366 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -152,6 +152,12 @@ impl Painter { pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 { self.ctx().round_pos_to_pixels(pos) } + + /// Useful for pixel-perfect rendering. + #[inline] + pub fn round_rect_to_pixels(&self, rect: Rect) -> Rect { + self.ctx().round_rect_to_pixels(rect) + } } /// ## Low level diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 6b77e8a14ecd..a5b19e14d9db 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -702,6 +702,116 @@ impl std::ops::Add for Margin { } } +impl std::ops::Add for Margin { + type Output = Self; + + #[inline] + fn add(self, v: f32) -> Self { + Self { + left: self.left + v, + right: self.right + v, + top: self.top + v, + bottom: self.bottom + v, + } + } +} + +impl std::ops::AddAssign for Margin { + #[inline] + fn add_assign(&mut self, v: f32) { + self.left += v; + self.right += v; + self.top += v; + self.bottom += v; + } +} + +impl std::ops::Div for Margin { + type Output = Self; + + #[inline] + fn div(self, v: f32) -> Self { + Self { + left: self.left / v, + right: self.right / v, + top: self.top / v, + bottom: self.bottom / v, + } + } +} + +impl std::ops::DivAssign for Margin { + #[inline] + fn div_assign(&mut self, v: f32) { + self.left /= v; + self.right /= v; + self.top /= v; + self.bottom /= v; + } +} + +impl std::ops::Mul for Margin { + type Output = Self; + + #[inline] + fn mul(self, v: f32) -> Self { + Self { + left: self.left * v, + right: self.right * v, + top: self.top * v, + bottom: self.bottom * v, + } + } +} + +impl std::ops::MulAssign for Margin { + #[inline] + fn mul_assign(&mut self, v: f32) { + self.left *= v; + self.right *= v; + self.top *= v; + self.bottom *= v; + } +} + +impl std::ops::Sub for Margin { + type Output = Self; + + #[inline] + fn sub(self, other: Self) -> Self { + Self { + left: self.left - other.left, + right: self.right - other.right, + top: self.top - other.top, + bottom: self.bottom - other.bottom, + } + } +} + +impl std::ops::Sub for Margin { + type Output = Self; + + #[inline] + fn sub(self, v: f32) -> Self { + Self { + left: self.left - v, + right: self.right - v, + top: self.top - v, + bottom: self.bottom - v, + } + } +} + +impl std::ops::SubAssign for Margin { + #[inline] + fn sub_assign(&mut self, v: f32) { + self.left -= v; + self.right -= v; + self.top -= v; + self.bottom -= v; + } +} + // ---------------------------------------------------------------------------- /// How and when interaction happens. diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 89048ac4841d..0aeec5525a57 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -755,6 +755,130 @@ impl Rounding { } } +impl std::ops::Add for Rounding { + type Output = Self; + #[inline] + fn add(self, rhs: Self) -> Self { + Self { + nw: self.nw + rhs.nw, + ne: self.ne + rhs.ne, + sw: self.sw + rhs.sw, + se: self.se + rhs.se, + } + } +} + +impl std::ops::AddAssign for Rounding { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = Self { + nw: self.nw + rhs.nw, + ne: self.ne + rhs.ne, + sw: self.sw + rhs.sw, + se: self.se + rhs.se, + }; + } +} + +impl std::ops::AddAssign for Rounding { + #[inline] + fn add_assign(&mut self, rhs: f32) { + *self = Self { + nw: self.nw + rhs, + ne: self.ne + rhs, + sw: self.sw + rhs, + se: self.se + rhs, + }; + } +} + +impl std::ops::Sub for Rounding { + type Output = Self; + #[inline] + fn sub(self, rhs: Self) -> Self { + Self { + nw: self.nw - rhs.nw, + ne: self.ne - rhs.ne, + sw: self.sw - rhs.sw, + se: self.se - rhs.se, + } + } +} + +impl std::ops::SubAssign for Rounding { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = Self { + nw: self.nw - rhs.nw, + ne: self.ne - rhs.ne, + sw: self.sw - rhs.sw, + se: self.se - rhs.se, + }; + } +} + +impl std::ops::SubAssign for Rounding { + #[inline] + fn sub_assign(&mut self, rhs: f32) { + *self = Self { + nw: self.nw - rhs, + ne: self.ne - rhs, + sw: self.sw - rhs, + se: self.se - rhs, + }; + } +} + +impl std::ops::Div for Rounding { + type Output = Self; + #[inline] + fn div(self, rhs: f32) -> Self { + Self { + nw: self.nw / rhs, + ne: self.ne / rhs, + sw: self.sw / rhs, + se: self.se / rhs, + } + } +} + +impl std::ops::DivAssign for Rounding { + #[inline] + fn div_assign(&mut self, rhs: f32) { + *self = Self { + nw: self.nw / rhs, + ne: self.ne / rhs, + sw: self.sw / rhs, + se: self.se / rhs, + }; + } +} + +impl std::ops::Mul for Rounding { + type Output = Self; + #[inline] + fn mul(self, rhs: f32) -> Self { + Self { + nw: self.nw * rhs, + ne: self.ne * rhs, + sw: self.sw * rhs, + se: self.se * rhs, + } + } +} + +impl std::ops::MulAssign for Rounding { + #[inline] + fn mul_assign(&mut self, rhs: f32) { + *self = Self { + nw: self.nw * rhs, + ne: self.ne * rhs, + sw: self.sw * rhs, + se: self.se * rhs, + }; + } +} + // ---------------------------------------------------------------------------- /// How to paint some text on screen.