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..eade55c91e8 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)) + }); } }