From 65b0676ae8f0a6ab6fc329a7bee8feb8e3efe908 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Tue, 2 Apr 2024 14:16:58 +0200 Subject: [PATCH 1/5] Make scroll animation configurable via Style and scroll_to_*_animation functions --- crates/egui/src/containers/scroll_area.rs | 22 ++++--- crates/egui/src/frame_state.rs | 8 +-- crates/egui/src/response.rs | 12 +++- crates/egui/src/style.rs | 76 +++++++++++++++++++++++ crates/egui/src/ui.rs | 31 ++++++++- 5 files changed, 132 insertions(+), 17 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index e564fbe1e4a..bfe3cc614b3 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -799,7 +799,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. @@ -808,7 +809,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]; @@ -817,7 +818,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 = @@ -834,11 +835,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; @@ -850,10 +857,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, diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 184ab0d64a8..e612c4d902b 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -79,7 +79,7 @@ pub struct FrameState { pub used_by_panels: Rect, /// The current scroll area should scroll to this range (horizontal, vertical). - pub scroll_target: [Option<(Rangef, Option)>; 2], + pub scroll_target: [Option<(Rangef, Option, style::ScrollAnimation)>; 2], /// The current scroll area should scroll by this much. /// @@ -90,7 +90,7 @@ pub 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 scroll_delta: Vec2, + pub scroll_delta: (Vec2, style::ScrollAnimation), #[cfg(feature = "accesskit")] pub accesskit_state: Option, @@ -113,7 +113,7 @@ impl Default for FrameState { unused_rect: Rect::NAN, used_by_panels: Rect::NAN, scroll_target: [None, None], - scroll_delta: Vec2::default(), + scroll_delta: (Vec2::default(), style::ScrollAnimation::none()), #[cfg(feature = "accesskit")] accesskit_state: None, highlight_next_frame: Default::default(), @@ -153,7 +153,7 @@ impl FrameState { *unused_rect = screen_rect; *used_by_panels = Rect::NOTHING; *scroll_target = [None, None]; - *scroll_delta = Vec2::default(); + *scroll_delta = (Vec2::default(), style::ScrollAnimation::none()); #[cfg(debug_assertions)] { diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 04b11fde280..1b9a2a6b445 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -846,9 +846,17 @@ impl Response { /// # }); /// ``` pub fn scroll_to_me(&self, align: Option) { + self.scroll_to_me_animation(align, self.ctx.style().scroll_animation); + } + + pub fn scroll_to_me_animation( + &self, + align: Option, + animation: crate::style::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)); }); } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 6009a6b6376..159c28f77fb 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -280,6 +280,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, } #[test] @@ -692,6 +695,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( + DragValue::new(&mut self.points_per_second) + .speed(100.0) + .range(0.0..=5000.0), + ); + ui.label("points/second"); + ui.end_row(); + + ui.label("Min duration:"); + ui.add( + DragValue::new(&mut self.min_duration) + .speed(0.01) + .range(0.0..=self.max_duration), + ); + ui.label("seconds"); + ui.end_row(); + + ui.label("Max duration:"); + ui.add( + DragValue::new(&mut self.max_duration) + .speed(0.01) + .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))] @@ -1129,6 +1202,7 @@ impl Default for Style { explanation_tooltips: false, url_in_tooltip: false, always_scroll_the_only_direction: false, + scroll_animation: ScrollAnimation::default(), } } } @@ -1425,6 +1499,7 @@ impl Style { explanation_tooltips, url_in_tooltip, always_scroll_the_only_direction, + scroll_animation, } = self; visuals.light_dark_radio_buttons(ui); @@ -1488,6 +1563,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)); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index a7a7712b7e2..90a7049ecbb 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1215,10 +1215,20 @@ impl Ui { /// # }); /// ``` pub fn scroll_to_rect(&self, rect: Rect, align: Option) { + self.scroll_to_rect_animation(rect, align, self.style.scroll_animation); + } + + /// Same as [`Self::scroll_to_rect`], but allows you to specify the [`style::ScrollAnimation`]. + pub fn scroll_to_rect_animation( + &self, + rect: Rect, + align: Option, + animation: style::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))); } } @@ -1245,11 +1255,20 @@ impl Ui { /// # }); /// ``` pub fn scroll_to_cursor(&self, align: Option) { + self.scroll_to_cursor_animation(align, self.style.scroll_animation); + } + + /// Same as [`Self::scroll_to_cursor`], but allows you to specify the [`style::ScrollAnimation`]. + pub fn scroll_to_cursor_animation( + &self, + align: Option, + animation: style::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))); } } @@ -1283,8 +1302,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 [`style::ScrollAnimation`]. + pub fn scroll_with_delta_animation(&self, delta: Vec2, animation: style::ScrollAnimation) { self.ctx().frame_state_mut(|state| { - state.scroll_delta += delta; + state.scroll_delta.0 += delta; + state.scroll_delta.1 = animation; }); } } From 902817e7a257f0231c9d6eecc6dcc0b2b3f9b71c Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 4 Jul 2024 18:55:28 +0200 Subject: [PATCH 2/5] Apply suggestions --- crates/egui/src/containers/scroll_area.rs | 10 +++--- crates/egui/src/frame_state.rs | 21 ++++++++++-- crates/egui/src/response.rs | 17 +++++++--- crates/egui/src/style.rs | 40 +++++++++++++++-------- crates/egui/src/ui.rs | 13 +++++--- 5 files changed, 70 insertions(+), 31 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index bfe3cc614b3..fcf5e0a7904 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -809,20 +809,20 @@ impl Prepared { .frame_state_mut(|state| state.scroll_target[d].take()); if scroll_enabled[d] { - let update = if let Some((target_range, align, animation)) = scroll_target { + let update = if let Some(target) = 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 (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 delta = if let Some(align) = target.align { let center_factor = align.to_factor(); let offset = - lerp(target_range, center_factor) - lerp(visible_range, center_factor); + 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); @@ -859,7 +859,7 @@ impl Prepared { // The further we scroll, the more time we take. let now = ui.input(|i| i.time); let animation_duration = (delta.abs() / animation.points_per_second) - .clamp(animation.min_duration, animation.max_duration); + .clamp(animation.duration.min, animation.duration.max); state.offset_target[d] = Some(ScrollTarget { animation_time_span: (now, now + animation_duration as f64), target_offset, diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index e612c4d902b..2a4a6b25028 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -40,6 +40,23 @@ pub struct PerLayerState { pub widget_with_tooltip: Option, } +#[derive(Clone, Debug)] +pub struct ScrollTarget { + pub range: Rangef, + pub align: Option, + pub animation: style::ScrollAnimation, +} + +impl ScrollTarget { + pub fn new(range: Rangef, align: Option, animation: style::ScrollAnimation) -> Self { + Self { + range, + align, + animation, + } + } +} + #[cfg(feature = "accesskit")] #[derive(Clone)] pub struct AccessKitFrameState { @@ -79,7 +96,7 @@ pub struct FrameState { pub used_by_panels: Rect, /// The current scroll area should scroll to this range (horizontal, vertical). - pub scroll_target: [Option<(Rangef, Option, style::ScrollAnimation)>; 2], + pub scroll_target: [Option; 2], /// The current scroll area should scroll by this much. /// @@ -153,7 +170,7 @@ impl FrameState { *unused_rect = screen_rect; *used_by_panels = Rect::NOTHING; *scroll_target = [None, None]; - *scroll_delta = (Vec2::default(), style::ScrollAnimation::none()); + *scroll_delta = Default::default(); #[cfg(debug_assertions)] { diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 1b9a2a6b445..146fbc64342 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -2,10 +2,9 @@ use std::{any::Any, sync::Arc}; use crate::{ emath::{Align, Pos2, Rect, Vec2}, - menu, AreaState, Context, CursorIcon, Id, LayerId, Order, PointerButton, Sense, Ui, WidgetRect, - WidgetText, + frame_state, menu, AreaState, Context, CursorIcon, Id, LayerId, Order, PointerButton, Sense, + Ui, WidgetRect, WidgetText, }; - // ---------------------------------------------------------------------------- /// The result of adding a widget to a [`Ui`]. @@ -855,8 +854,16 @@ impl Response { animation: crate::style::ScrollAnimation, ) { self.ctx.frame_state_mut(|state| { - state.scroll_target[0] = Some((self.rect.x_range(), align, animation)); - state.scroll_target[1] = Some((self.rect.y_range(), align, animation)); + state.scroll_target[0] = Some(frame_state::ScrollTarget::new( + self.rect.x_range(), + align, + animation, + )); + state.scroll_target[1] = Some(frame_state::ScrollTarget::new( + self.rect.y_range(), + align, + animation, + )); }); } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 159c28f77fb..67f80c056c9 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -695,39 +695,51 @@ impl ScrollStyle { // ---------------------------------------------------------------------------- -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +/// Scroll animation configuration, used when programmatically scrolling somewhere (e.g. with `[crate::Ui::scroll_to_cursor]`) +/// The animation duration is calculated based on the distance to be scrolled via `[ScrollAnimation::points_per_second]` +/// and can be clamped to a min / max duration via `[ScrollAnimation::duration]`. +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct ScrollAnimation { + /// With what speed should we scroll? (Default: 1000.0) pub points_per_second: f32, - pub min_duration: f32, - pub max_duration: f32, + + /// The min / max scroll duration. + pub duration: Rangef, } impl Default for ScrollAnimation { fn default() -> Self { Self { points_per_second: 1000.0, - min_duration: 0.1, - max_duration: 0.3, + duration: Rangef::new(0.1, 0.3), } } } impl ScrollAnimation { + /// New scroll animation + pub fn new(points_per_second: f32, duration: Rangef) -> Self { + Self { + points_per_second, + duration, + } + } + + /// No animation, scroll instantly. pub fn none() -> Self { Self { - points_per_second: 0.0, - min_duration: 0.0, - max_duration: 0.0, + points_per_second: f32::INFINITY, + duration: Rangef::new(0.0, 0.0), } } + /// Scroll with a fixed duration, regardless of distance. pub fn duration(t: f32) -> Self { Self { - points_per_second: 0.0, - min_duration: t, - max_duration: t, + points_per_second: f32::INFINITY, + duration: Rangef::new(t, t), } } @@ -744,16 +756,16 @@ impl ScrollAnimation { ui.label("Min duration:"); ui.add( - DragValue::new(&mut self.min_duration) + DragValue::new(&mut self.duration.min) .speed(0.01) - .range(0.0..=self.max_duration), + .range(0.0..=self.duration.max), ); ui.label("seconds"); ui.end_row(); ui.label("Max duration:"); ui.add( - DragValue::new(&mut self.max_duration) + DragValue::new(&mut self.duration.max) .speed(0.01) .range(0.0..=1.0), ); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 90a7049ecbb..6da4a28d632 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -9,7 +9,6 @@ use crate::{ containers::*, ecolor::*, epaint::text::Fonts, layout::*, menu::MenuState, placer::Placer, util::IdTypeMap, widgets::*, *, }; - // ---------------------------------------------------------------------------- /// This is what you use to place widgets. @@ -1227,8 +1226,10 @@ impl Ui { ) { 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, animation))); + self.ctx().frame_state_mut(|state| { + state.scroll_target[d] = + Some(frame_state::ScrollTarget::new(range, align, animation)); + }); } } @@ -1267,8 +1268,10 @@ impl Ui { 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, animation))); + self.ctx().frame_state_mut(|state| { + state.scroll_target[d] = + Some(frame_state::ScrollTarget::new(target, align, animation)); + }); } } From 4013e1927c055882f791c8591d94ca5a63177dca Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Fri, 5 Jul 2024 15:10:43 +0200 Subject: [PATCH 3/5] Destructure ScrollTarget --- crates/egui/src/containers/scroll_area.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index fcf5e0a7904..1685ec1b694 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -809,20 +809,25 @@ impl Prepared { .frame_state_mut(|state| state.scroll_target[d].take()); if scroll_enabled[d] { - let update = if let Some(target) = scroll_target { + if let Some(target) = scroll_target { + let frame_state::ScrollTarget { + range, + align, + animation: animation_update, + } = 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 (start, end) = (range.min, 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) = target.align { + let delta_update = if let Some(align) = align { let center_factor = align.to_factor(); let offset = - lerp(target.range, center_factor) - lerp(visible_range, center_factor); + lerp(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); @@ -836,15 +841,10 @@ impl Prepared { // Ui is already in view, no need to adjust scroll. 0.0 }; - Some((delta, animation)) - } else { - 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; From 3be1cbad98c6c3875a5bc049ebb61aa028d85e11 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 17 Jul 2024 19:27:36 +0200 Subject: [PATCH 4/5] Rename scroll_area::ScrollTarget to ScrollingToTarget --- crates/egui/src/containers/scroll_area.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 1685ec1b694..3f0e3a2df35 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -4,7 +4,7 @@ use crate::*; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -struct ScrollTarget { +struct ScrollingToTarget { animation_time_span: (f64, f64), target_offset: f32, } @@ -17,7 +17,7 @@ pub struct State { pub offset: Vec2, /// If set, quickly but smoothly scroll to this target offset. - offset_target: [Option; 2], + offset_target: [Option; 2], /// Were the scroll bars visible last frame? show_scroll: Vec2b, @@ -860,7 +860,7 @@ impl Prepared { let now = ui.input(|i| i.time); let animation_duration = (delta.abs() / animation.points_per_second) .clamp(animation.duration.min, animation.duration.max); - state.offset_target[d] = Some(ScrollTarget { + state.offset_target[d] = Some(ScrollingToTarget { animation_time_span: (now, now + animation_duration as f64), target_offset, }); From b393b718638f5c6c47a7f601d65ed93ba3933a2a Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 17 Jul 2024 19:33:47 +0200 Subject: [PATCH 5/5] Add some more documentation --- crates/egui/src/frame_state.rs | 7 +++++++ crates/egui/src/response.rs | 1 + 2 files changed, 8 insertions(+) diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 2a4a6b25028..8670daffdef 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -42,8 +42,15 @@ pub struct PerLayerState { #[derive(Clone, Debug)] pub struct ScrollTarget { + // The range that the scroll area should scroll to. pub range: Rangef, + + /// How should we align the rect within the visible area? + /// If `align` is [`Align::TOP`] it means "put the top of the rect at the top of the scroll area", etc. + /// If `align` is `None`, it'll scroll enough to bring the UI into view. pub align: Option, + + /// How should the scroll be animated? pub animation: style::ScrollAnimation, } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 146fbc64342..e0775258674 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -848,6 +848,7 @@ impl Response { self.scroll_to_me_animation(align, self.ctx.style().scroll_animation); } + /// Like [`Self::scroll_to_me`], but allows you to specify the [`crate::style::ScrollAnimation`]. pub fn scroll_to_me_animation( &self, align: Option,