`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)
This commit is contained in:
Varphone Wong 2024-03-08 17:32:23 +08:00 committed by GitHub
parent 1f414c059d
commit a93c6cd5d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 300 additions and 25 deletions

View File

@ -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<Stroke>,
color: impl Into<Color32>,
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(

View File

@ -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<Stroke>,
rounding: impl Into<Rounding>,
) {
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:

View File

@ -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

View File

@ -702,6 +702,116 @@ impl std::ops::Add for Margin {
}
}
impl std::ops::Add<f32> 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<f32> 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<f32> 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<f32> 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<f32> 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<f32> 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<f32> 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<f32> 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.

View File

@ -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<f32> 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<f32> 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<f32> 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<f32> 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<f32> 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<f32> 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.