Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make scroll_to_* animations configurable #4305

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 14 additions & 8 deletions crates/egui/src/containers/scroll_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,8 @@ impl Prepared {

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];
let mut delta = -scroll_delta.0[d];
let mut animation = scroll_delta.1;

// We always take both scroll targets regardless of which scroll axes are enabled. This
// is to avoid them leaking to other scroll areas.
Expand All @@ -798,7 +799,7 @@ impl Prepared {
.frame_state_mut(|state| state.scroll_target[d].take());

if scroll_enabled[d] {
delta += if let Some((target_range, align)) = scroll_target {
let update = if let Some((target_range, align, animation)) = 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 @@ -807,7 +808,7 @@ impl Prepared {
let clip_end = clip_rect.max[d];
let mut spacing = ui.spacing().item_spacing[d];

if let Some(align) = align {
let delta = if let Some(align) = align {
let center_factor = align.to_factor();

let offset =
Expand All @@ -824,11 +825,17 @@ impl Prepared {
} else {
// Ui is already in view, no need to adjust scroll.
0.0
}
};
Some((delta, animation))
} else {
0.0
None
};

if let Some((delta_update, animation_update)) = update {
delta += delta_update;
animation = animation_update;
}

if delta != 0.0 {
let target_offset = state.offset[d] + delta;

Expand All @@ -840,10 +847,9 @@ impl Prepared {
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);
let animation_duration = (delta.abs() / animation.points_per_second)
.clamp(animation.min_duration, animation.max_duration);
state.offset_target[d] = Some(ScrollTarget {
animation_time_span: (now, now + animation_duration as f64),
target_offset,
Expand Down
9 changes: 5 additions & 4 deletions crates/egui/src/frame_state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::style::ScrollAnimation;
use crate::{id::IdSet, *};

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -39,7 +40,7 @@ pub(crate) struct FrameState {
pub(crate) tooltip_state: Option<TooltipFrameState>,

/// 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>, ScrollAnimation)>; 2],

/// The current scroll area should scroll by this much.
///
Expand All @@ -50,7 +51,7 @@ pub(crate) struct FrameState {
///
/// 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,
pub(crate) scroll_delta: (Vec2, ScrollAnimation),

#[cfg(feature = "accesskit")]
pub(crate) accesskit_state: Option<AccessKitFrameState>,
Expand All @@ -74,7 +75,7 @@ impl Default for FrameState {
used_by_panels: Rect::NAN,
tooltip_state: None,
scroll_target: [None, None],
scroll_delta: Vec2::default(),
scroll_delta: (Vec2::default(), ScrollAnimation::none()),
#[cfg(feature = "accesskit")]
accesskit_state: None,
highlight_this_frame: Default::default(),
Expand Down Expand Up @@ -112,7 +113,7 @@ impl FrameState {
*used_by_panels = Rect::NOTHING;
*tooltip_state = None;
*scroll_target = [None, None];
*scroll_delta = Vec2::default();
*scroll_delta = (Vec2::default(), ScrollAnimation::none());

#[cfg(debug_assertions)]
{
Expand Down
9 changes: 7 additions & 2 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{any::Any, sync::Arc};

use crate::style::ScrollAnimation;
use crate::{
emath::{Align, Pos2, Rect, Vec2},
menu, ComboBox, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetRect,
Expand Down Expand Up @@ -739,9 +740,13 @@ impl Response {
/// # });
/// ```
pub fn scroll_to_me(&self, align: Option<Align>) {
self.scroll_to_me_animation(align, self.ctx.style().scroll_animation);
}

pub fn scroll_to_me_animation(&self, align: Option<Align>, animation: ScrollAnimation) {
self.ctx.frame_state_mut(|state| {
state.scroll_target[0] = Some((self.rect.x_range(), align));
state.scroll_target[1] = Some((self.rect.y_range(), align));
state.scroll_target[0] = Some((self.rect.x_range(), align, animation));
state.scroll_target[1] = Some((self.rect.y_range(), align, animation));
});
}

Expand Down
76 changes: 76 additions & 0 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ pub struct Style {

/// If true and scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift
pub always_scroll_the_only_direction: bool,

/// The animation that should be used when scrolling a [`crate::ScrollArea`] using e.g. [Ui::scroll_to_rect].
pub scroll_animation: ScrollAnimation,
}

impl Style {
Expand Down Expand Up @@ -624,6 +627,76 @@ impl ScrollStyle {

// ----------------------------------------------------------------------------

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct ScrollAnimation {
pub points_per_second: f32,
pub min_duration: f32,
pub max_duration: f32,
}

impl Default for ScrollAnimation {
fn default() -> Self {
Self {
points_per_second: 1000.0,
min_duration: 0.1,
max_duration: 0.3,
}
}
}

impl ScrollAnimation {
pub fn none() -> Self {
Self {
points_per_second: 0.0,
min_duration: 0.0,
max_duration: 0.0,
}
}

pub fn duration(t: f32) -> Self {
Self {
points_per_second: 0.0,
min_duration: t,
max_duration: t,
}
}

pub fn ui(&mut self, ui: &mut crate::Ui) {
crate::Grid::new("scroll_animation").show(ui, |ui| {
ui.label("Scroll animation:");
ui.add(
crate::DragValue::new(&mut self.points_per_second)
.speed(100.0)
.clamp_range(0.0..=5000.0),
);
ui.label("points/second");
ui.end_row();

ui.label("Min duration:");
ui.add(
crate::DragValue::new(&mut self.min_duration)
.speed(0.01)
.clamp_range(0.0..=self.max_duration),
);
ui.label("seconds");
ui.end_row();

ui.label("Max duration:");
ui.add(
crate::DragValue::new(&mut self.max_duration)
.speed(0.01)
.clamp_range(0.0..=1.0),
);
ui.label("seconds");
ui.end_row();
});
}
}

// ----------------------------------------------------------------------------

/// How and when interaction happens.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand Down Expand Up @@ -1053,6 +1126,7 @@ impl Default for Style {
explanation_tooltips: false,
url_in_tooltip: false,
always_scroll_the_only_direction: false,
scroll_animation: ScrollAnimation::default(),
}
}
}
Expand Down Expand Up @@ -1346,6 +1420,7 @@ impl Style {
explanation_tooltips,
url_in_tooltip,
always_scroll_the_only_direction,
scroll_animation,
} = self;

visuals.light_dark_radio_buttons(ui);
Expand Down Expand Up @@ -1409,6 +1484,7 @@ impl Style {
ui.collapsing("📏 Spacing", |ui| spacing.ui(ui));
ui.collapsing("☝ Interaction", |ui| interaction.ui(ui));
ui.collapsing("🎨 Visuals", |ui| visuals.ui(ui));
ui.collapsing("🔄 Scroll Animation", |ui| scroll_animation.ui(ui));

#[cfg(debug_assertions)]
ui.collapsing("🐛 Debug", |ui| debug.ui(ui));
Expand Down
28 changes: 25 additions & 3 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{any::Any, hash::Hash, sync::Arc};

use epaint::mutex::RwLock;

use crate::style::ScrollAnimation;
use crate::{
containers::*, ecolor::*, epaint::text::Fonts, layout::*, menu::MenuState, placer::Placer,
util::IdTypeMap, widgets::*, *,
Expand Down Expand Up @@ -1052,10 +1053,20 @@ impl Ui {
/// # });
/// ```
pub fn scroll_to_rect(&self, rect: Rect, align: Option<Align>) {
self.scroll_to_rect_animation(rect, align, self.style.scroll_animation);
}

/// Same as [`Self::scroll_to_rect`], but allows you to specify the [`ScrollAnimation`].
pub fn scroll_to_rect_animation(
&self,
rect: Rect,
align: Option<Align>,
animation: ScrollAnimation,
) {
for d in 0..2 {
let range = Rangef::new(rect.min[d], rect.max[d]);
self.ctx()
.frame_state_mut(|state| state.scroll_target[d] = Some((range, align)));
.frame_state_mut(|state| state.scroll_target[d] = Some((range, align, animation)));
}
}

Expand All @@ -1082,11 +1093,16 @@ impl Ui {
/// # });
/// ```
pub fn scroll_to_cursor(&self, align: Option<Align>) {
self.scroll_to_cursor_animation(align, self.style.scroll_animation);
}

/// Same as [`Self::scroll_to_cursor`], but allows you to specify the [`ScrollAnimation`].
pub fn scroll_to_cursor_animation(&self, align: Option<Align>, animation: ScrollAnimation) {
let target = self.next_widget_position();
for d in 0..2 {
let target = Rangef::point(target[d]);
self.ctx()
.frame_state_mut(|state| state.scroll_target[d] = Some((target, align)));
.frame_state_mut(|state| state.scroll_target[d] = Some((target, align, animation)));
}
}

Expand Down Expand Up @@ -1120,8 +1136,14 @@ impl Ui {
/// # });
/// ```
pub fn scroll_with_delta(&self, delta: Vec2) {
self.scroll_with_delta_animation(delta, self.style.scroll_animation);
}

/// Same as [`Self::scroll_with_delta`], but allows you to specify the [`ScrollAnimation`].
pub fn scroll_with_delta_animation(&self, delta: Vec2, animation: ScrollAnimation) {
self.ctx().frame_state_mut(|state| {
state.scroll_delta += delta;
state.scroll_delta.0 += delta;
state.scroll_delta.1 = animation;
});
}
}
Expand Down