From 1dea8fac9b95aeb99631585e53c73045923a97d8 Mon Sep 17 00:00:00 2001 From: Gilberto Santos Date: Wed, 5 Mar 2025 10:49:04 +0100 Subject: [PATCH] Fix scroll handle extending outside of `ScrollArea` (#5286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [x] I have followed the instructions in the PR template * Closes https://github.com/emilk/egui/issues/3734 ## Info This PR addresses an issue where resizing a scroll handle can lead to unwanted overlap. It is also happening on the egui-demo. ![Screenshot 2024-10-18 at 17 35 25](https://github.com/user-attachments/assets/3a9527d9-fc46-4b25-b95a-9ba2fa54978e) ## Cause *Note: The following explanation assumes a vertical scroll; however, the logic applies equally to horizontal scrolling.* When the scroll handle is positioned at the top or bottom of the scroll area and the handle is resized to fit the minimum handle size, there is a risk of overlap. This occurs if the handle’s new size extends beyond the bounds of the scroll area. ## Proposed Solution 1. Check whether increasing the handle size will cause it to overlap with the scroll area. 2. If an overlap is detected, adjust the handle’s center position by the overlap amount, moving it towards the center of the scroll area. --- crates/egui/src/containers/scroll_area.rs | 74 ++++++++++------------- 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 727a9da7..9ec2263f 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,8 +1,8 @@ #![allow(clippy::needless_range_loop)] use crate::{ - emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, vec2, Context, Id, NumExt, Pos2, - Rangef, Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, + emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, Context, Id, NumExt, Pos2, Rangef, + Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, }; #[derive(Clone, Copy, Debug)] @@ -1090,21 +1090,35 @@ impl Prepared { ) }; - let handle_rect = if d == 0 { - Rect::from_min_max( - pos2(from_content(state.offset.x), cross.min), - pos2(from_content(state.offset.x + inner_rect.width()), cross.max), - ) - } else { - Rect::from_min_max( - pos2(cross.min, from_content(state.offset.y)), - pos2( - cross.max, - from_content(state.offset.y + inner_rect.height()), - ), - ) + let calculate_handle_rect = |d, offset: &Vec2| { + let handle_size = if d == 0 { + from_content(offset.x + inner_rect.width()) - from_content(offset.x) + } else { + from_content(offset.y + inner_rect.height()) - from_content(offset.y) + } + .max(scroll_style.handle_min_length); + + let handle_start_point = remap_clamp( + offset[d], + 0.0..=max_offset[d], + scroll_bar_rect.min[d]..=(scroll_bar_rect.max[d] - handle_size), + ); + + if d == 0 { + Rect::from_min_max( + pos2(handle_start_point, cross.min), + pos2(handle_start_point + handle_size, cross.max), + ) + } else { + Rect::from_min_max( + pos2(cross.min, handle_start_point), + pos2(cross.max, handle_start_point + handle_size), + ) + } }; + let handle_rect = calculate_handle_rect(d, &state.offset); + let interact_id = id.with(d); let sense = if self.scrolling_enabled { Sense::click_and_drag() @@ -1133,8 +1147,8 @@ impl Prepared { let new_handle_top = pointer_pos[d] - *scroll_start_offset_from_top_left; state.offset[d] = remap( new_handle_top, - scroll_bar_rect.min[d]..=scroll_bar_rect.max[d], - 0.0..=content_size[d], + scroll_bar_rect.min[d]..=(scroll_bar_rect.max[d] - handle_rect.size()[d]), + 0.0..=max_offset[d], ); // some manual action taken, scroll not stuck @@ -1154,31 +1168,7 @@ impl Prepared { if ui.is_rect_visible(outer_scroll_bar_rect) { // Avoid frame-delay by calculating a new handle rect: - let mut handle_rect = if d == 0 { - Rect::from_min_max( - pos2(from_content(state.offset.x), cross.min), - pos2(from_content(state.offset.x + inner_rect.width()), cross.max), - ) - } else { - Rect::from_min_max( - pos2(cross.min, from_content(state.offset.y)), - pos2( - cross.max, - from_content(state.offset.y + inner_rect.height()), - ), - ) - }; - let min_handle_size = scroll_style.handle_min_length; - if handle_rect.size()[d] < min_handle_size { - handle_rect = Rect::from_center_size( - handle_rect.center(), - if d == 0 { - vec2(min_handle_size, handle_rect.size().y) - } else { - vec2(handle_rect.size().x, min_handle_size) - }, - ); - } + let handle_rect = calculate_handle_rect(d, &state.offset); let visuals = if scrolling_enabled { // Pick visuals based on interaction with the handle.