From 73bb4cedb4624ccec4cb52cc19a6be576a35ad88 Mon Sep 17 00:00:00 2001 From: Douglas Dwyer Date: Tue, 27 Aug 2024 04:22:32 -0400 Subject: [PATCH] Prevent `ScrollArea` contents from exceeding the container size (#5006) When a `ScrollArea` is added to a `Ui` or its contents change dynamically, the contents will briefly escape the container. This occurs because `ScrollArea` internally maintains `content_is_too_large` flags, from which it determines when to clip. The `content_is_too_large` flags are calculated after painting, so they always lag one frame behind. This can lead to flickering. To fix this, I have changed the `ScrollArea` so that it always clips scrollable content. I believe that this should fix things without negatively impacting other behavior. To see this, consider how `ScrollArea` calculates the `content_is_too_large` flag: ```rust // This calculates a new inner rect, after painting, from the initial clip rect let inner_rect = { // At this point this is the available size for the inner rect. let mut inner_size = inner_rect.size(); for d in 0..2 { inner_size[d] = match (scroll_enabled[d], auto_shrink[d]) { (true, true) => inner_size[d].min(content_size[d]), // shrink scroll area if content is small (true, false) => inner_size[d], // let scroll area be larger than content; fill with blank space (false, true) => content_size[d], // Follow the content (expand/contract to fit it). (false, false) => inner_size[d].max(content_size[d]), // Expand to fit content }; } Rect::from_min_size(inner_rect.min, inner_size) }; let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use); let content_is_too_large = Vec2b::new( scroll_enabled[0] && inner_rect.width() < content_size.x, scroll_enabled[1] && inner_rect.height() < content_size.y, ); ``` If `scroll_enabled[d] == true`, then the actual `inner_rect` (which is calculated after painting contents) will always be smaller than the original `inner_rect`. Hence, it is safe to unconditionally clip the contents to `inner_rect` whenever `scroll_enabled[d] == true`. * Closes * [x] I have followed the instructions in the PR template --- crates/egui/src/containers/scroll_area.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 20fd4e4f7c5..aaccb2d7209 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -572,10 +572,8 @@ impl ScrollArea { let mut content_clip_rect = ui.clip_rect(); for d in 0..2 { if scroll_enabled[d] { - if state.content_is_too_large[d] { - content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin; - content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin; - } + content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin; + content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin; } else { // Nice handling of forced resizing beyond the possible: content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d];