diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 700c2040253..11b9f18e917 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -871,7 +871,13 @@ impl Prepared { let max_offset = content_size - inner_rect.size(); let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect); - if scrolling_enabled && is_hovering_outer_rect { + + if scrolling_enabled + && (is_hovering_outer_rect + || scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible) + { + ui.ctx().create_scroll_delta(inner_rect); + let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction && scroll_enabled[0] != scroll_enabled[1]; for d in 0..2 { @@ -890,20 +896,19 @@ impl Prepared { if scrolling_up || scrolling_down { state.offset[d] -= scroll_delta; - - // Clear scroll delta so no parent scroll will use it: - ui.ctx().input_mut(|input| { - if always_scroll_enabled_direction { - input.smooth_scroll_delta[0] = 0.0; - input.smooth_scroll_delta[1] = 0.0; - } else { - input.smooth_scroll_delta[d] = 0.0; - } - }); - state.scroll_stuck_to_end[d] = false; state.offset_target[d] = None; } + + // Clear scroll delta so no parent scroll will use it: + ui.ctx().input_mut(|input| { + if always_scroll_enabled_direction { + input.smooth_scroll_delta[0] = 0.0; + input.smooth_scroll_delta[1] = 0.0; + } else { + input.smooth_scroll_delta[d] = 0.0; + } + }); } } } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index d0dedaa1ac1..ca42de8abae 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1079,6 +1079,17 @@ impl Context { .map_or(false, |response| response.contains_pointer) } + /// Create scroll delta + /// + /// See [`InputState`], [`ScrollArea`] + pub(crate) fn create_scroll_delta(&self, inner_rect: Rect) { + self.input_mut(|input| { + let pointer_position = input.pointer.interact_pos().unwrap_or_default(); + let is_contains_pointer = inner_rect.contains(pointer_position); + input.create_scroll_delta(false, is_contains_pointer); + }); + } + /// Do all interaction for an existing widget, without (re-)registering it. fn get_response(&self, widget_rect: WidgetRect) -> Response { let WidgetRect { diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index f52bfe24928..17ae70ae274 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -299,7 +299,7 @@ impl InputState { } } - { + if unprocessed_scroll_delta_for_zoom != 0.0 { // Smooth scroll-to-zoom: if unprocessed_scroll_delta_for_zoom.abs() < 1.0 { smooth_scroll_delta_for_zoom += unprocessed_scroll_delta_for_zoom; @@ -321,7 +321,7 @@ impl InputState { unprocessed_scroll_delta, unprocessed_scroll_delta_for_zoom, raw_scroll_delta, - smooth_scroll_delta, + smooth_scroll_delta: self.smooth_scroll_delta, zoom_factor_delta, screen_rect, pixels_per_point, @@ -338,6 +338,39 @@ impl InputState { } } + pub fn create_scroll_delta(&mut self, is_begin_frame: bool, is_contains_pointer: bool) { + if !is_begin_frame && !is_contains_pointer { + return; + } + + if !is_begin_frame && is_contains_pointer { + self.unprocessed_scroll_delta += self.raw_scroll_delta; + self.raw_scroll_delta = Vec2::ZERO; + } + + if self.unprocessed_scroll_delta != Vec2::ZERO { + let dt = self.stable_dt.at_most(0.1); + let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); + + for d in 0..2 { + if self.unprocessed_scroll_delta[d].abs() < 1.0 { + self.smooth_scroll_delta[d] += self.unprocessed_scroll_delta[d]; + self.unprocessed_scroll_delta[d] = 0.0; + } else { + let smooth_delta = t * self.unprocessed_scroll_delta[d]; + let direct_delta = self.unprocessed_scroll_delta[d]; + // Smooth: smooth_delta > 0.0, Direct: smooth_delta < 0.0 + let applied = match smooth_delta < 0.0 { + true => smooth_delta.min(direct_delta), + false => smooth_delta.max(direct_delta), + }; + self.smooth_scroll_delta[d] += applied; + self.unprocessed_scroll_delta[d] -= applied; + } + } + } + } + /// Info about the active viewport #[inline] pub fn viewport(&self) -> &ViewportInfo {