`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) { pub fn paint_resize_corner(ui: &Ui, response: &Response) {
let stroke = ui.style().interact(response).fg_stroke; 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( pub fn paint_resize_corner_with_style(
ui: &Ui, ui: &Ui,
rect: &Rect, rect: &Rect,
stroke: impl Into<Stroke>, color: impl Into<Color32>,
corner: Align2, corner: Align2,
) { ) {
let painter = ui.painter(); let painter = ui.painter();
let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect)); let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect));
let mut w = 2.0; 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() { while w <= rect.width() && w <= rect.height() {
painter.line_segment( painter.line_segment(

View File

@ -392,7 +392,12 @@ impl<'open> Window<'open> {
let header_color = let header_color =
frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill); 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_explicitly_closed = matches!(open, Some(false));
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); 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 // 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 (title_bar_height, title_content_spacing) = if with_title_bar {
let style = ctx.style(); let style = ctx.style();
let window_margin = window_frame.inner_margin;
let spacing = window_margin.top + window_margin.bottom; let spacing = window_margin.top + window_margin.bottom;
let height = ctx.fonts(|f| title.font_height(f, &style)) + spacing; 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) (height, spacing)
} else { } else {
(0.0, 0.0) (0.0, 0.0)
@ -495,21 +501,33 @@ impl<'open> Window<'open> {
.map_or((None, None), |ir| (Some(ir.inner), Some(ir.response))); .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response)));
let outer_rect = frame.end(&mut area_content_ui).rect; 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 -------------------------------- // END FRAME --------------------------------
if let Some(title_bar) = title_bar { if let Some(title_bar) = title_bar {
if on_top && area_content_ui.visuals().window_highlight_topmost { let mut title_rect = Rect::from_min_size(
let rect = Rect::from_min_size( outer_rect.min + vec2(border_padding, border_padding),
outer_rect.min, Vec2 {
Vec2 { x: outer_rect.size().x - border_padding * 2.0,
x: outer_rect.size().x, y: title_bar_height,
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; let mut round = window_frame.rounding;
// Eliminate the rounding gap between the title bar and the window frame
round -= border_padding;
if !is_collapsed { if !is_collapsed {
round.se = 0.0; round.se = 0.0;
round.sw = 0.0; round.sw = 0.0;
@ -517,18 +535,18 @@ impl<'open> Window<'open> {
area_content_ui.painter().set( area_content_ui.painter().set(
*where_to_put_header_background, *where_to_put_header_background,
RectShape::filled(rect, round, header_color), RectShape::filled(title_rect, round, header_color),
); );
}; };
// Fix title bar separator line position // Fix title bar separator line position
if let Some(response) = &mut content_response { 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( title_bar.ui(
&mut area_content_ui, &mut area_content_ui,
outer_rect, title_rect,
&content_response, &content_response,
open, open,
&mut collapsing, &mut collapsing,
@ -558,23 +576,34 @@ fn paint_resize_corner(
possible: &PossibleInteractions, possible: &PossibleInteractions,
outer_rect: Rect, outer_rect: Rect,
stroke: impl Into<Stroke>, stroke: impl Into<Stroke>,
rounding: impl Into<Rounding>,
) { ) {
let corner = if possible.resize_right && possible.resize_bottom { let stroke = stroke.into();
Align2::RIGHT_BOTTOM 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 { } else if possible.resize_left && possible.resize_bottom {
Align2::LEFT_BOTTOM (Align2::LEFT_BOTTOM, rounding.sw)
} else if possible.resize_left && possible.resize_top { } else if possible.resize_left && possible.resize_top {
Align2::LEFT_TOP (Align2::LEFT_TOP, rounding.nw)
} else if possible.resize_right && possible.resize_top { } else if possible.resize_right && possible.resize_top {
Align2::RIGHT_TOP (Align2::RIGHT_TOP, rounding.ne)
} else { } else {
return; 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_size = Vec2::splat(ui.visuals().resize_corner_size);
let corner_rect = corner.align_size_within_rect(corner_size, outer_rect); 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 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, 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 = content_response.rect.top();
// let y = lerp(self.rect.bottom()..=content_response.rect.top(), 0.5); // let y = lerp(self.rect.bottom()..=content_response.rect.top(), 0.5);
let stroke = ui.visuals().widgets.noninteractive.bg_stroke; 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: // 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 { pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
self.ctx().round_pos_to_pixels(pos) 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 /// ## 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. /// 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. /// How to paint some text on screen.