Fix `Ui::scroll_with_delta` only scrolling if the `ScrollArea` is focused (#4303)
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to add commits to your PR. * Remember to run `cargo fmt` and `cargo cranky`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> This introduces the boolean field force_current_scroll_area to InputState which will be set when scroll_with_delta is called, causing the ScrollArea to skip the check whether it is focused and always consume the smooth scroll delta. * Closes #2783 * Related to #4295 --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
6f59a14c4d
commit
4b59c6d414
|
|
@ -783,7 +783,14 @@ impl Prepared {
|
||||||
|
|
||||||
let content_size = content_ui.min_size();
|
let content_size = content_ui.min_size();
|
||||||
|
|
||||||
|
let scroll_delta = content_ui
|
||||||
|
.ctx()
|
||||||
|
.frame_state_mut(|state| std::mem::take(&mut state.scroll_delta));
|
||||||
|
|
||||||
for d in 0..2 {
|
for d in 0..2 {
|
||||||
|
// FrameState::scroll_delta is inverted from the way we apply the delta, so we need to negate it.
|
||||||
|
let mut delta = -scroll_delta[d];
|
||||||
|
|
||||||
// We always take both scroll targets regardless of which scroll axes are enabled. This
|
// We always take both scroll targets regardless of which scroll axes are enabled. This
|
||||||
// is to avoid them leaking to other scroll areas.
|
// is to avoid them leaking to other scroll areas.
|
||||||
let scroll_target = content_ui
|
let scroll_target = content_ui
|
||||||
|
|
@ -791,7 +798,7 @@ impl Prepared {
|
||||||
.frame_state_mut(|state| state.scroll_target[d].take());
|
.frame_state_mut(|state| state.scroll_target[d].take());
|
||||||
|
|
||||||
if scroll_enabled[d] {
|
if scroll_enabled[d] {
|
||||||
if let Some((target_range, align)) = scroll_target {
|
delta += if let Some((target_range, align)) = scroll_target {
|
||||||
let min = content_ui.min_rect().min[d];
|
let min = content_ui.min_rect().min[d];
|
||||||
let clip_rect = content_ui.clip_rect();
|
let clip_rect = content_ui.clip_rect();
|
||||||
let visible_range = min..=min + clip_rect.size()[d];
|
let visible_range = min..=min + clip_rect.size()[d];
|
||||||
|
|
@ -800,7 +807,7 @@ impl Prepared {
|
||||||
let clip_end = clip_rect.max[d];
|
let clip_end = clip_rect.max[d];
|
||||||
let mut spacing = ui.spacing().item_spacing[d];
|
let mut spacing = ui.spacing().item_spacing[d];
|
||||||
|
|
||||||
let delta = if let Some(align) = align {
|
if let Some(align) = align {
|
||||||
let center_factor = align.to_factor();
|
let center_factor = align.to_factor();
|
||||||
|
|
||||||
let offset =
|
let offset =
|
||||||
|
|
@ -817,31 +824,32 @@ impl Prepared {
|
||||||
} else {
|
} else {
|
||||||
// Ui is already in view, no need to adjust scroll.
|
// Ui is already in view, no need to adjust scroll.
|
||||||
0.0
|
0.0
|
||||||
};
|
|
||||||
|
|
||||||
if delta != 0.0 {
|
|
||||||
let target_offset = state.offset[d] + delta;
|
|
||||||
|
|
||||||
if !animated {
|
|
||||||
state.offset[d] = target_offset;
|
|
||||||
} else if let Some(animation) = &mut state.offset_target[d] {
|
|
||||||
// For instance: the user is continuously calling `ui.scroll_to_cursor`,
|
|
||||||
// so we don't want to reset the animation, but perhaps update the target:
|
|
||||||
animation.target_offset = target_offset;
|
|
||||||
} else {
|
|
||||||
// The further we scroll, the more time we take.
|
|
||||||
// TODO(emilk): let users configure this in `Style`.
|
|
||||||
let now = ui.input(|i| i.time);
|
|
||||||
let points_per_second = 1000.0;
|
|
||||||
let animation_duration =
|
|
||||||
(delta.abs() / points_per_second).clamp(0.1, 0.3);
|
|
||||||
state.offset_target[d] = Some(ScrollTarget {
|
|
||||||
animation_time_span: (now, now + animation_duration as f64),
|
|
||||||
target_offset,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.ctx().request_repaint();
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
if delta != 0.0 {
|
||||||
|
let target_offset = state.offset[d] + delta;
|
||||||
|
|
||||||
|
if !animated {
|
||||||
|
state.offset[d] = target_offset;
|
||||||
|
} else if let Some(animation) = &mut state.offset_target[d] {
|
||||||
|
// For instance: the user is continuously calling `ui.scroll_to_cursor`,
|
||||||
|
// so we don't want to reset the animation, but perhaps update the target:
|
||||||
|
animation.target_offset = target_offset;
|
||||||
|
} else {
|
||||||
|
// The further we scroll, the more time we take.
|
||||||
|
// TODO(emilk): let users configure this in `Style`.
|
||||||
|
let now = ui.input(|i| i.time);
|
||||||
|
let points_per_second = 1000.0;
|
||||||
|
let animation_duration = (delta.abs() / points_per_second).clamp(0.1, 0.3);
|
||||||
|
state.offset_target[d] = Some(ScrollTarget {
|
||||||
|
animation_time_span: (now, now + animation_duration as f64),
|
||||||
|
target_offset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ui.ctx().request_repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,20 @@ pub(crate) struct FrameState {
|
||||||
/// Initialized to `None` at the start of each frame.
|
/// Initialized to `None` at the start of each frame.
|
||||||
pub(crate) tooltip_state: Option<TooltipFrameState>,
|
pub(crate) tooltip_state: Option<TooltipFrameState>,
|
||||||
|
|
||||||
/// horizontal, vertical
|
/// The current scroll area should scroll to this range (horizontal, vertical).
|
||||||
pub(crate) scroll_target: [Option<(Rangef, Option<Align>)>; 2],
|
pub(crate) scroll_target: [Option<(Rangef, Option<Align>)>; 2],
|
||||||
|
|
||||||
|
/// The current scroll area should scroll by this much.
|
||||||
|
///
|
||||||
|
/// The delta dictates how the _content_ should move.
|
||||||
|
///
|
||||||
|
/// A positive X-value indicates the content is being moved right,
|
||||||
|
/// as when swiping right on a touch-screen or track-pad with natural scrolling.
|
||||||
|
///
|
||||||
|
/// A positive Y-value indicates the content is being moved down,
|
||||||
|
/// as when swiping down on a touch-screen or track-pad with natural scrolling.
|
||||||
|
pub(crate) scroll_delta: Vec2,
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
pub(crate) accesskit_state: Option<AccessKitFrameState>,
|
pub(crate) accesskit_state: Option<AccessKitFrameState>,
|
||||||
|
|
||||||
|
|
@ -63,6 +74,7 @@ impl Default for FrameState {
|
||||||
used_by_panels: Rect::NAN,
|
used_by_panels: Rect::NAN,
|
||||||
tooltip_state: None,
|
tooltip_state: None,
|
||||||
scroll_target: [None, None],
|
scroll_target: [None, None],
|
||||||
|
scroll_delta: Vec2::default(),
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
accesskit_state: None,
|
accesskit_state: None,
|
||||||
highlight_this_frame: Default::default(),
|
highlight_this_frame: Default::default(),
|
||||||
|
|
@ -84,6 +96,7 @@ impl FrameState {
|
||||||
used_by_panels,
|
used_by_panels,
|
||||||
tooltip_state,
|
tooltip_state,
|
||||||
scroll_target,
|
scroll_target,
|
||||||
|
scroll_delta,
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
accesskit_state,
|
accesskit_state,
|
||||||
highlight_this_frame,
|
highlight_this_frame,
|
||||||
|
|
@ -99,6 +112,7 @@ impl FrameState {
|
||||||
*used_by_panels = Rect::NOTHING;
|
*used_by_panels = Rect::NOTHING;
|
||||||
*tooltip_state = None;
|
*tooltip_state = None;
|
||||||
*scroll_target = [None, None];
|
*scroll_target = [None, None];
|
||||||
|
*scroll_delta = Vec2::default();
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1075,6 +1075,8 @@ impl Ui {
|
||||||
/// A positive Y-value indicates the content is being moved down,
|
/// A positive Y-value indicates the content is being moved down,
|
||||||
/// as when swiping down on a touch-screen or track-pad with natural scrolling.
|
/// as when swiping down on a touch-screen or track-pad with natural scrolling.
|
||||||
///
|
///
|
||||||
|
/// If this is called multiple times per frame for the same [`ScrollArea`], the deltas will be summed.
|
||||||
|
///
|
||||||
/// /// See also: [`Response::scroll_to_me`], [`Ui::scroll_to_rect`], [`Ui::scroll_to_cursor`]
|
/// /// See also: [`Response::scroll_to_me`], [`Ui::scroll_to_rect`], [`Ui::scroll_to_cursor`]
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -1093,8 +1095,9 @@ impl Ui {
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn scroll_with_delta(&self, delta: Vec2) {
|
pub fn scroll_with_delta(&self, delta: Vec2) {
|
||||||
self.ctx()
|
self.ctx().frame_state_mut(|state| {
|
||||||
.input_mut(|input| input.smooth_scroll_delta += delta);
|
state.scroll_delta += delta;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ struct ScrollTo {
|
||||||
track_item: usize,
|
track_item: usize,
|
||||||
tack_item_align: Option<Align>,
|
tack_item_align: Option<Align>,
|
||||||
offset: f32,
|
offset: f32,
|
||||||
|
delta: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ScrollTo {
|
impl Default for ScrollTo {
|
||||||
|
|
@ -244,6 +245,7 @@ impl Default for ScrollTo {
|
||||||
track_item: 25,
|
track_item: 25,
|
||||||
tack_item_align: Some(Align::Center),
|
tack_item_align: Some(Align::Center),
|
||||||
offset: 0.0,
|
offset: 0.0,
|
||||||
|
delta: 64.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -258,6 +260,7 @@ impl super::View for ScrollTo {
|
||||||
let mut go_to_scroll_offset = false;
|
let mut go_to_scroll_offset = false;
|
||||||
let mut scroll_top = false;
|
let mut scroll_top = false;
|
||||||
let mut scroll_bottom = false;
|
let mut scroll_bottom = false;
|
||||||
|
let mut scroll_delta = None;
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Scroll to a specific item index:");
|
ui.label("Scroll to a specific item index:");
|
||||||
|
|
@ -294,6 +297,20 @@ impl super::View for ScrollTo {
|
||||||
scroll_bottom |= ui.button("Scroll to bottom").clicked();
|
scroll_bottom |= ui.button("Scroll to bottom").clicked();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Scroll by");
|
||||||
|
DragValue::new(&mut self.delta)
|
||||||
|
.speed(1.0)
|
||||||
|
.suffix("px")
|
||||||
|
.ui(ui);
|
||||||
|
if ui.button("⬇").clicked() {
|
||||||
|
scroll_delta = Some(self.delta * Vec2::UP); // scroll down (move contents up)
|
||||||
|
}
|
||||||
|
if ui.button("⬆").clicked() {
|
||||||
|
scroll_delta = Some(self.delta * Vec2::DOWN); // scroll up (move contents down)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut scroll_area = ScrollArea::vertical().max_height(200.0).auto_shrink(false);
|
let mut scroll_area = ScrollArea::vertical().max_height(200.0).auto_shrink(false);
|
||||||
if go_to_scroll_offset {
|
if go_to_scroll_offset {
|
||||||
scroll_area = scroll_area.vertical_scroll_offset(self.offset);
|
scroll_area = scroll_area.vertical_scroll_offset(self.offset);
|
||||||
|
|
@ -305,6 +322,10 @@ impl super::View for ScrollTo {
|
||||||
if scroll_top {
|
if scroll_top {
|
||||||
ui.scroll_to_cursor(Some(Align::TOP));
|
ui.scroll_to_cursor(Some(Align::TOP));
|
||||||
}
|
}
|
||||||
|
if let Some(scroll_delta) = scroll_delta {
|
||||||
|
ui.scroll_with_delta(scroll_delta);
|
||||||
|
}
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
for item in 1..=num_items {
|
for item in 1..=num_items {
|
||||||
if track_item && item == self.track_item {
|
if track_item && item == self.track_item {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue