Skip to content

Commit

Permalink
Fix Ui::scroll_with_delta only scrolling if the ScrollArea is foc…
Browse files Browse the repository at this point in the history
…used (emilk#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 emilk#2783 
* Related to emilk#4295

---------

Co-authored-by: Emil Ernerfeldt <[email protected]>
  • Loading branch information
2 people authored and hacknus committed Oct 30, 2024
1 parent 7e6505d commit c335bee
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 27 deletions.
56 changes: 32 additions & 24 deletions crates/egui/src/containers/scroll_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,15 +783,22 @@ impl Prepared {

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 {
// 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
// is to avoid them leaking to other scroll areas.
let scroll_target = content_ui
.ctx()
.frame_state_mut(|state| state.scroll_target[d].take());

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 clip_rect = content_ui.clip_rect();
let visible_range = min..=min + clip_rect.size()[d];
Expand All @@ -800,7 +807,7 @@ impl Prepared {
let clip_end = clip_rect.max[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 offset =
Expand All @@ -817,31 +824,32 @@ impl Prepared {
} else {
// Ui is already in view, no need to adjust scroll.
0.0
};
}
} else {
0.0
};

if delta != 0.0 {
let target_offset = state.offset[d] + delta;
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();
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();
}
}
}
Expand Down
16 changes: 15 additions & 1 deletion crates/egui/src/frame_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,20 @@ pub(crate) struct FrameState {
/// Initialized to `None` at the start of each frame.
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],

/// 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")]
pub(crate) accesskit_state: Option<AccessKitFrameState>,

Expand All @@ -63,6 +74,7 @@ impl Default for FrameState {
used_by_panels: Rect::NAN,
tooltip_state: None,
scroll_target: [None, None],
scroll_delta: Vec2::default(),
#[cfg(feature = "accesskit")]
accesskit_state: None,
highlight_this_frame: Default::default(),
Expand All @@ -84,6 +96,7 @@ impl FrameState {
used_by_panels,
tooltip_state,
scroll_target,
scroll_delta,
#[cfg(feature = "accesskit")]
accesskit_state,
highlight_this_frame,
Expand All @@ -99,6 +112,7 @@ impl FrameState {
*used_by_panels = Rect::NOTHING;
*tooltip_state = None;
*scroll_target = [None, None];
*scroll_delta = Vec2::default();

#[cfg(debug_assertions)]
{
Expand Down
7 changes: 5 additions & 2 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,8 @@ impl Ui {
/// 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.
///
/// 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`]
///
/// ```
Expand All @@ -1093,8 +1095,9 @@ impl Ui {
/// # });
/// ```
pub fn scroll_with_delta(&self, delta: Vec2) {
self.ctx()
.input_mut(|input| input.smooth_scroll_delta += delta);
self.ctx().frame_state_mut(|state| {
state.scroll_delta += delta;
});
}
}

Expand Down
21 changes: 21 additions & 0 deletions crates/egui_demo_lib/src/demo/scrolling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ struct ScrollTo {
track_item: usize,
tack_item_align: Option<Align>,
offset: f32,
delta: f32,
}

impl Default for ScrollTo {
Expand All @@ -244,6 +245,7 @@ impl Default for ScrollTo {
track_item: 25,
tack_item_align: Some(Align::Center),
offset: 0.0,
delta: 64.0,
}
}
}
Expand All @@ -258,6 +260,7 @@ impl super::View for ScrollTo {
let mut go_to_scroll_offset = false;
let mut scroll_top = false;
let mut scroll_bottom = false;
let mut scroll_delta = None;

ui.horizontal(|ui| {
ui.label("Scroll to a specific item index:");
Expand Down Expand Up @@ -294,6 +297,20 @@ impl super::View for ScrollTo {
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);
if go_to_scroll_offset {
scroll_area = scroll_area.vertical_scroll_offset(self.offset);
Expand All @@ -305,6 +322,10 @@ impl super::View for ScrollTo {
if scroll_top {
ui.scroll_to_cursor(Some(Align::TOP));
}
if let Some(scroll_delta) = scroll_delta {
ui.scroll_with_delta(scroll_delta);
}

ui.vertical(|ui| {
for item in 1..=num_items {
if track_item && item == self.track_item {
Expand Down

0 comments on commit c335bee

Please sign in to comment.