Prevent widgets sometimes appearing to move relative to each other (#7710)

Sometimes when moving a window, having a tooltip attached to the mouse
pointer, or scrolling a `ScrollArea`, you would see this disturbing
effect:


![drift-bug](https://github.com/user-attachments/assets/013a5f49-ee02-417c-8441-1e1a0369e8bd)

This is caused by us rounding many visual elements (lines, rectangles,
text, …) to physical pixels in order to keep them sharp. If the
window/tooltip itself is not rounded to a physical pixel, then you can
get this behavior.

So from now on the position of all
areas/windows/tooltips/popups/ScrollArea gets rounded to the closes
pixel.

* Unlocked by https://github.com/emilk/egui/pull/7709
This commit is contained in:
Emil Ernerfeldt 2025-11-13 10:52:46 +01:00
parent 33cc8ef180
commit 787c467d30
8 changed files with 37 additions and 18 deletions

View File

@ -553,13 +553,14 @@ impl Area {
move_response
};
if constrain {
state.set_left_top_pos(
Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min,
);
}
state.set_left_top_pos(state.left_top_pos());
state.set_left_top_pos(round_area_position(
ctx,
if constrain {
Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min
} else {
state.left_top_pos()
},
));
// Update response with possibly moved/constrained rect:
move_response.rect = state.rect();
@ -580,6 +581,16 @@ impl Area {
}
}
fn round_area_position(ctx: &Context, pos: Pos2) -> Pos2 {
// We round a lot of rendering to pixels, so we round the whole
// area positions to pixels too, so avoid widgets appearing to float
// around independently of each other when the area is dragged.
// But just in case pixels_per_point is irrational,
// we then also round to ui coordinates:
pos.round_to_pixels(ctx.pixels_per_point()).round_ui()
}
impl Prepared {
pub(crate) fn state(&self) -> &AreaState {
&self.state

View File

@ -2,6 +2,8 @@
use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
use emath::GuiRounding as _;
use crate::{
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp,
@ -748,6 +750,12 @@ impl ScrollArea {
}
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
// Round to pixels to avoid widgets appearing to "float" when scrolling fractional amounts:
let content_max_rect = content_max_rect
.round_to_pixels(ui.pixels_per_point())
.round_ui();
let mut content_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::ScrollArea))

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dc9c22567b76193a7f6753c4217adb3c92afa921c488ba1cf2e14b403814e7ac
size 99841
oid sha256:128ca4e741995ffcdc07b027407d63911ded6c94fe3fe1dd0efecbf9408fb3af
size 99871

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aff927596be5db77349ec0bbdcc852a0b1467e94c2a553a740a383ae318bad18
oid sha256:bb3f7b5f790830b46d1410c2bbb5e19c6beb403f8fe979eb8d250fba4f89be3e
size 51670

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d6f055247034fa13ab55c9ec1fca275e6c23999c9a7e01c87af1fcc930faac6
size 66777
oid sha256:170cee9d72a4ab59aa2faf1b77aff4a9eee64f3380aa3f1b256340d88b1dabc2
size 66525

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c91f592571ba654d0a96791662ae7530a1db4c1630b57c795d1c006ea6e46f19
size 256975
oid sha256:f7a7d0e2618b852b5966073438c95cb62901d5410c1473639920b0b0bf2ec59b
size 256913

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4fbcca2b13c94769a62b44853b19f7e841bbb60c9197b3d0bf6e83ef9f8f76d1
size 77815
oid sha256:72f4c6fe4f5ec243506152027e1150f3069caf98511ceef92b8fea4f6a1563d5
size 77614

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a31f0c12bb70449136443f9086103bd5b46356eedc2bb93ae1b6b10684ab69ca
size 36285
oid sha256:611a2d6c793a85eebe807b2ddd4446cc0bc21e4284343dd756e64f0232fb6815
size 35991