Skip to content

Commit

Permalink
Make scroll animation configurable via Style and scroll_to_*_animatio…
Browse files Browse the repository at this point in the history
…n functions
  • Loading branch information
lucasmerlin committed Apr 2, 2024
1 parent 74d74f9 commit 739c666
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 39 deletions.
68 changes: 36 additions & 32 deletions crates/egui/src/containers/scroll_area.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(clippy::needless_range_loop)]

use crate::style::ScrollAnimation;
use crate::*;

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -773,39 +774,42 @@ impl Prepared {
});

if scroll_enabled[d] {
let mut 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];
let (start, end) = (target_range.min, target_range.max);
let clip_start = clip_rect.min[d];
let clip_end = clip_rect.max[d];
let mut spacing = ui.spacing().item_spacing[d];

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

let offset =
lerp(target_range, center_factor) - lerp(visible_range, center_factor);

// Depending on the alignment we need to add or subtract the spacing
spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);

offset + spacing - state.offset[d]
} else if start < clip_start && end < clip_end {
-(clip_start - start + spacing).min(clip_end - end - spacing)
} else if end > clip_end && start > clip_start {
(end - clip_end + spacing).min(start - clip_start - spacing)
let (mut delta, mut animation) =
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];
let (start, end) = (target_range.min, target_range.max);
let clip_start = clip_rect.min[d];
let clip_end = clip_rect.max[d];
let mut spacing = ui.spacing().item_spacing[d];

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

let offset = lerp(target_range, center_factor)
- lerp(visible_range, center_factor);

// Depending on the alignment we need to add or subtract the spacing
spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);

offset + spacing - state.offset[d]
} else if start < clip_start && end < clip_end {
-(clip_start - start + spacing).min(clip_end - end - spacing)
} else if end > clip_end && start > clip_start {
(end - clip_end + spacing).min(start - clip_start - spacing)
} else {
// Ui is already in view, no need to adjust scroll.
0.0
};
(delta, animation)
} else {
// Ui is already in view, no need to adjust scroll.
0.0
}
} else {
0.0
};
(0.0, ScrollAnimation::none())
};

if let Some(scroll_delta) = scroll_delta {
if let Some((scroll_delta, scroll_animation)) = scroll_delta {
delta += scroll_delta;
animation = scroll_animation;
}

if delta != 0.0 {
Expand All @@ -819,8 +823,8 @@ impl Prepared {
// 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
5 changes: 3 additions & 2 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,10 +40,10 @@ 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 (horizontal, vertical).
pub(crate) scroll_delta: [Option<f32>; 2],
pub(crate) scroll_delta: [Option<(f32, ScrollAnimation)>; 2],

#[cfg(feature = "accesskit")]
pub(crate) accesskit_state: Option<AccessKitFrameState>,
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, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetRect, WidgetText,
Expand Down Expand Up @@ -734,9 +735,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 @@ -219,6 +219,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 @@ -614,6 +617,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 @@ -1041,6 +1114,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 @@ -1332,6 +1406,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 @@ -1395,6 +1470,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
24 changes: 21 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 @@ -1025,10 +1026,19 @@ impl Ui {
/// # });
/// ```
pub fn scroll_to_rect(&self, rect: Rect, align: Option<Align>) {
self.scroll_to_rect_animation(rect, align, self.style.scroll_animation)
}

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 @@ -1055,11 +1065,15 @@ impl Ui {
/// # });
/// ```
pub fn scroll_to_cursor(&self, align: Option<Align>) {
self.scroll_to_cursor_animation(align, self.style.scroll_animation)
}

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 @@ -1091,8 +1105,12 @@ impl Ui {
/// # });
/// ```
pub fn scroll_with_delta(&self, delta: Vec2) {
self.scroll_with_delta_animation(delta, self.style.scroll_animation)
}

pub fn scroll_with_delta_animation(&self, delta: Vec2, animation: ScrollAnimation) {
self.ctx().frame_state_mut(|state| {
state.scroll_delta = [Some(delta.x), Some(delta.y)];
state.scroll_delta = [Some((delta.x, animation)), Some((delta.y, animation))];
});
}
}
Expand Down

0 comments on commit 739c666

Please sign in to comment.