`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:
parent
1f414c059d
commit
a93c6cd5d2
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue