Skip to content

Commit

Permalink
egui: Fixed the incorrect display of the Window frame with a wide b…
Browse files Browse the repository at this point in the history
…order 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)
  • Loading branch information
varphone authored Mar 8, 2024
1 parent 1f414c0 commit a93c6cd
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 25 deletions.
9 changes: 6 additions & 3 deletions crates/egui/src/containers/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
76 changes: 54 additions & 22 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -495,40 +501,52 @@ 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;
}

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,
Expand Down Expand Up @@ -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);
}

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions crates/egui/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
110 changes: 110 additions & 0 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit a93c6cd

Please sign in to comment.