From a9cf94e06a305dd0ebdd101dc9410156c950e4dd Mon Sep 17 00:00:00 2001 From: tom-englert Date: Wed, 31 Jul 2024 13:29:44 +0200 Subject: [PATCH] Refactor and fix AdvancedScrollWheelBehavior (renamed from SmoothScrollingBehavior) --- .../Samples/AdvancedScrollingView.xaml | 105 ++++++++ ....xaml.cs => AdvancedScrollingView.xaml.cs} | 6 +- ...Model.cs => AdvancedScrollingViewModel.cs} | 4 +- .../Samples/SmoothScrollingView.xaml | 68 ----- ...vior.cs => AdvancedScrollWheelBehavior.cs} | 241 +++++++++++------- .../Interactivity/AdvancedScrollWheelMode.cs | 20 ++ .../TomsToolbox.Wpf.ExternalAnnotations.xml | 8 +- 7 files changed, 285 insertions(+), 167 deletions(-) create mode 100644 src/SampleApp/Samples/AdvancedScrollingView.xaml rename src/SampleApp/Samples/{SmoothScrollingView.xaml.cs => AdvancedScrollingView.xaml.cs} (63%) rename src/SampleApp/Samples/{SmoothScrollingViewModel.cs => AdvancedScrollingViewModel.cs} (83%) delete mode 100644 src/SampleApp/Samples/SmoothScrollingView.xaml rename src/TomsToolbox.Wpf/Interactivity/{SmoothScrollingBehavior.cs => AdvancedScrollWheelBehavior.cs} (57%) create mode 100644 src/TomsToolbox.Wpf/Interactivity/AdvancedScrollWheelMode.cs diff --git a/src/SampleApp/Samples/AdvancedScrollingView.xaml b/src/SampleApp/Samples/AdvancedScrollingView.xaml new file mode 100644 index 00000000..e8b670e3 --- /dev/null +++ b/src/SampleApp/Samples/AdvancedScrollingView.xaml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SampleApp/Samples/SmoothScrollingView.xaml.cs b/src/SampleApp/Samples/AdvancedScrollingView.xaml.cs similarity index 63% rename from src/SampleApp/Samples/SmoothScrollingView.xaml.cs rename to src/SampleApp/Samples/AdvancedScrollingView.xaml.cs index be0c5aa2..b75edf33 100644 --- a/src/SampleApp/Samples/SmoothScrollingView.xaml.cs +++ b/src/SampleApp/Samples/AdvancedScrollingView.xaml.cs @@ -7,10 +7,10 @@ /// /// Interaction logic for SmoothScrollingView.xaml /// -[DataTemplate(typeof(SmoothScrollingViewModel))] -public partial class SmoothScrollingView : UserControl +[DataTemplate(typeof(AdvancedScrollingViewModel))] +public partial class AdvancedScrollingView : UserControl { - public SmoothScrollingView() + public AdvancedScrollingView() { InitializeComponent(); } diff --git a/src/SampleApp/Samples/SmoothScrollingViewModel.cs b/src/SampleApp/Samples/AdvancedScrollingViewModel.cs similarity index 83% rename from src/SampleApp/Samples/SmoothScrollingViewModel.cs rename to src/SampleApp/Samples/AdvancedScrollingViewModel.cs index 7bd5dd18..6a472ca1 100644 --- a/src/SampleApp/Samples/SmoothScrollingViewModel.cs +++ b/src/SampleApp/Samples/AdvancedScrollingViewModel.cs @@ -8,13 +8,13 @@ namespace SampleApp.Samples; [Export] [VisualCompositionExport(RegionId.Main, Sequence = 12)] [Shared] -public partial class SmoothScrollingViewModel : INotifyPropertyChanged +public partial class AdvancedScrollingViewModel : INotifyPropertyChanged { private static readonly Random _randomNumberGenerator = new(); public override string ToString() { - return "SmoothScrolling"; + return "Advanced Scrolling"; } public ICollection SampleData { get; } = Enumerable.Range(1, 1000) diff --git a/src/SampleApp/Samples/SmoothScrollingView.xaml b/src/SampleApp/Samples/SmoothScrollingView.xaml deleted file mode 100644 index 4e2e47d3..00000000 --- a/src/SampleApp/Samples/SmoothScrollingView.xaml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/TomsToolbox.Wpf/Interactivity/SmoothScrollingBehavior.cs b/src/TomsToolbox.Wpf/Interactivity/AdvancedScrollWheelBehavior.cs similarity index 57% rename from src/TomsToolbox.Wpf/Interactivity/SmoothScrollingBehavior.cs rename to src/TomsToolbox.Wpf/Interactivity/AdvancedScrollWheelBehavior.cs index 8f770d7b..a0743c29 100644 --- a/src/TomsToolbox.Wpf/Interactivity/SmoothScrollingBehavior.cs +++ b/src/TomsToolbox.Wpf/Interactivity/AdvancedScrollWheelBehavior.cs @@ -1,6 +1,7 @@ namespace TomsToolbox.Wpf.Interactivity; using System; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Windows; @@ -11,41 +12,76 @@ using Microsoft.Xaml.Behaviors; +using TomsToolbox.Essentials; using TomsToolbox.Wpf; +[Obsolete("Use AdvancedScrollWheelBehavior instead.")] +// For backward compatibility, the SmoothScrollingBehavior is still available, but it is recommended to use the AdvancedScrollWheelBehavior instead. +public class SmoothScrollingBehavior : AdvancedScrollWheelBehavior +{ + /// + /// Sets the Register property for the specified element. + /// + /// The element to set the property for. + /// The value to set. + public static void SetRegister(DependencyObject element, bool value) + { + element.SetValue(RegisterProperty, value); + } + /// + /// Gets the Register property for the specified element. + /// + /// The element to get the property for. + /// The value of the Register property. + public static bool GetRegister(DependencyObject element) + { + return (bool)element.GetValue(RegisterProperty); + } + /// + /// Identifies the attached property. + /// + /// + /// + /// If set to true, the behavior is attached to the target element. This is a shortcut to avoid attaching the behavior in XAML when all defaults are used. + /// + /// + public static readonly DependencyProperty RegisterProperty = DependencyProperty.RegisterAttached( + "Register", typeof(bool), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(default(bool), Register_Changed)); + + private static void Register_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + SetAttach(d, (e.NewValue is true) ? AdvancedScrollWheelMode.WithAnimation : AdvancedScrollWheelMode.None); + } +} + /// -/// Provides smooth scrolling behavior for a control. +/// Provides advanced mouse wheel scrolling behavior for a control. +/// Makes the scrolling distance smoother, especially for touchpad scrolling, and optionally animated for mouse wheel scrolling. /// -public class SmoothScrollingBehavior : FrameworkElementBehavior +public class AdvancedScrollWheelBehavior : FrameworkElementBehavior { private const long MillisecondsBetweenTouchpadScrolling = 100; - private delegate bool GetBoolDelegate(ScrollViewer scrollViewer); private delegate IScrollInfo GetScrollInfoDelegate(ScrollViewer scrollViewer); private static readonly GetScrollInfoDelegate _propertyScrollInfoGetter = (GetScrollInfoDelegate)typeof(ScrollViewer).GetProperty("ScrollInfo", BindingFlags.Instance | BindingFlags.NonPublic)! .GetGetMethod(true)! .CreateDelegate(typeof(GetScrollInfoDelegate)); - private static readonly GetBoolDelegate _propertyHandlesMouseWheelScrollingGetter = (GetBoolDelegate)typeof(ScrollViewer) - .GetProperty("HandlesMouseWheelScrolling", BindingFlags.Instance | BindingFlags.NonPublic)! - .GetGetMethod(true)! - .CreateDelegate(typeof(GetBoolDelegate)); - - private static readonly IEasingFunction _scrollingAnimationEase = new CubicEase { EasingMode = EasingMode.EaseOut }; - private ScrollViewer? _scrollViewer; private ScrollContentPresenter? _scrollContentPresenter; private double _horizontalOffsetTarget; private double _verticalOffsetTarget; - private bool _isAnimationRunning; - private int _lastScrollDelta; - private int _lastVerticalScrollingDelta; - private int _lastHorizontalScrollingDelta; + private bool _lastScrollWasTouchPad; private long _lastScrollingTick; + private int _animationIdCounter; + private DoubleAnimation? _currentAnimation; + + private bool IsAnimationRunning => _currentAnimation != null; + /// protected override void OnAssociatedObjectLoaded() { @@ -57,6 +93,11 @@ protected override void OnAssociatedObjectLoaded() return; _scrollViewer.PreviewMouseWheel += ScrollViewer_PreviewMouseWheel; + _scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; + + _horizontalOffsetTarget = _scrollViewer.HorizontalOffset; + _verticalOffsetTarget = _scrollViewer.VerticalOffset; + _scrollContentPresenter = _scrollViewer.VisualDescendants().OfType().FirstOrDefault(); } @@ -69,8 +110,11 @@ protected override void OnAssociatedObjectUnloaded() return; _scrollViewer.PreviewMouseWheel -= ScrollViewer_PreviewMouseWheel; + _scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged; } + #region DependencyProperties + /// /// Gets or sets a value indicating whether the smooth scrolling behavior is enabled. /// @@ -83,10 +127,10 @@ public bool IsEnabled /// Identifies the dependency property. /// public static readonly DependencyProperty IsEnabledProperty = - DependencyProperty.Register(nameof(IsEnabled), typeof(bool), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(true)); + DependencyProperty.Register(nameof(IsEnabled), typeof(bool), typeof(AdvancedScrollWheelBehavior), new FrameworkPropertyMetadata(true)); /// - /// Gets or sets a value indicating whether to use scrolling animation when scolling using the mouse wheel. + /// Gets or sets a value indicating whether to use scrolling animation when scrolling using the mouse wheel. /// public bool UseScrollingAnimation { @@ -97,7 +141,7 @@ public bool UseScrollingAnimation /// Identifies the dependency property. /// public static readonly DependencyProperty UseScrollingAnimationProperty = - DependencyProperty.Register(nameof(UseScrollingAnimation), typeof(bool), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(true)); + DependencyProperty.Register(nameof(UseScrollingAnimation), typeof(bool), typeof(AdvancedScrollWheelBehavior), new FrameworkPropertyMetadata(true)); /// /// Gets or sets the duration of the scrolling animation. @@ -111,7 +155,7 @@ public Duration ScrollingAnimationDuration /// Identifies the dependency property. /// public static readonly DependencyProperty ScrollingAnimationDurationProperty = - DependencyProperty.Register(nameof(ScrollingAnimationDuration), typeof(Duration), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(new Duration(TimeSpan.FromMilliseconds(250))), ValidateScrollingAnimationDuration); + DependencyProperty.Register(nameof(ScrollingAnimationDuration), typeof(Duration), typeof(AdvancedScrollWheelBehavior), new FrameworkPropertyMetadata(new Duration(TimeSpan.FromMilliseconds(250))), ValidateScrollingAnimationDuration); private static bool ValidateScrollingAnimationDuration(object value) => value is Duration { HasTimeSpan: true }; @@ -128,7 +172,7 @@ public double MouseScrollDeltaFactor /// Identifies the dependency property. /// public static readonly DependencyProperty MouseScrollDeltaFactorProperty = - DependencyProperty.Register(nameof(MouseScrollDeltaFactor), typeof(double), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(1.0)); + DependencyProperty.Register(nameof(MouseScrollDeltaFactor), typeof(double), typeof(AdvancedScrollWheelBehavior), new FrameworkPropertyMetadata(1.0)); /// /// Gets or sets the delta value factor while touchpad scrolling. @@ -142,78 +186,90 @@ public double TouchpadScrollDeltaFactor /// Identifies the dependency property. /// public static readonly DependencyProperty TouchpadScrollDeltaFactorProperty = - DependencyProperty.Register(nameof(TouchpadScrollDeltaFactor), typeof(double), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(2.0)); + DependencyProperty.Register(nameof(TouchpadScrollDeltaFactor), typeof(double), typeof(AdvancedScrollWheelBehavior), new FrameworkPropertyMetadata(2.0)); /// - /// Gets or sets a value indicating whether to always handle mouse wheel scrolling, even if the control has opted out (e.g., "TextBox"). + /// Gets or sets the easing function used for the scrolling animation. /// - public bool AlwaysHandleMouseWheelScrolling + public IEasingFunction? EasingFunction { - get { return (bool)GetValue(AlwaysHandleMouseWheelScrollingProperty); } - set { SetValue(AlwaysHandleMouseWheelScrollingProperty, value); } + get { return (IEasingFunction)GetValue(EasingFunctionProperty); } + set { SetValue(EasingFunctionProperty, value); } } /// - /// Identifies the dependency property. + /// Identifies the dependency property. /// - public static readonly DependencyProperty AlwaysHandleMouseWheelScrollingProperty = - DependencyProperty.Register(nameof(AlwaysHandleMouseWheelScrolling), typeof(bool), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(false)); + public static readonly DependencyProperty EasingFunctionProperty = DependencyProperty.Register( + nameof(EasingFunction), typeof(IEasingFunction), typeof(AdvancedScrollWheelBehavior)); /// /// Sets the Register property for the specified element. /// /// The element to set the property for. /// The value to set. - public static void SetRegister(DependencyObject element, bool value) + public static void SetAttach(DependencyObject element, AdvancedScrollWheelMode value) { - element.SetValue(RegisterProperty, value); + element.SetValue(AttachProperty, value); } /// /// Gets the Register property for the specified element. /// /// The element to get the property for. /// The value of the Register property. - public static bool GetRegister(DependencyObject element) + public static AdvancedScrollWheelMode GetAttach(DependencyObject element) { - return (bool)element.GetValue(RegisterProperty); + return (AdvancedScrollWheelMode)element.GetValue(AttachProperty); } /// - /// Identifies the attached property. + /// Identifies the attached property. /// /// /// - /// If set to true, the behavior is attached to the target element. This is a shortcut to avoid attaching the behavior in XAML when all defaults are used. + /// If set to or , the behavior is attached to the target element with default settings. + /// This is a shortcut to omit the full behavior notation in XAML when only defaults are used. /// /// - public static readonly DependencyProperty RegisterProperty = DependencyProperty.RegisterAttached( - "Register", typeof(bool), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(default(bool), Register_Changed)); + public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached( + "Attach", typeof(AdvancedScrollWheelMode), typeof(AdvancedScrollWheelBehavior), new FrameworkPropertyMetadata(default(AdvancedScrollWheelMode), Attach_Changed)); - private static void Register_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void Attach_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (e.NewValue as bool? != true) - return; - var behaviors = Interaction.GetBehaviors(d); - if (behaviors.OfType().Any()) - return; - - behaviors.Add(new SmoothScrollingBehavior()); + + behaviors.RemoveWhere(item => item is AdvancedScrollWheelBehavior); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (e.NewValue as AdvancedScrollWheelMode?) + { + case AdvancedScrollWheelMode.WithAnimation: + behaviors.Add(new AdvancedScrollWheelBehavior { UseScrollingAnimation = true }); + break; + + case AdvancedScrollWheelMode.WithoutAnimation: + behaviors.Add(new AdvancedScrollWheelBehavior { UseScrollingAnimation = false }); + break; + } } - private static readonly DependencyProperty VerticalOffsetProperty = - DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(0.0, VerticalOffset_Changed)); + #endregion - private static void VerticalOffset_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static readonly DependencyProperty AnimatedVerticalOffsetProperty = + DependencyProperty.RegisterAttached("AnimatedVerticalOffset", typeof(double), typeof(AdvancedScrollWheelBehavior), new FrameworkPropertyMetadata(0.0, AnimatedVerticalOffset_Changed)); + + private static void AnimatedVerticalOffset_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((e.NewValue is not double offset) || (d is not ScrollViewer scrollViewer)) return; + Debug.WriteLine($"Offset: {offset}"); + scrollViewer.ScrollToVerticalOffset(offset); } - private static readonly DependencyProperty HorizontalOffsetProperty = - DependencyProperty.RegisterAttached("HorizontalOffset", typeof(double), typeof(SmoothScrollingBehavior), new FrameworkPropertyMetadata(0.0, HorizontalOffset_Changed)); + private static readonly DependencyProperty AnimatedHorizontalOffsetProperty = + DependencyProperty.RegisterAttached("AnimatedHorizontalOffset", typeof(double), typeof(AdvancedScrollWheelBehavior), new FrameworkPropertyMetadata(0.0, AnimatedHorizontalOffset_Changed)); - private static void HorizontalOffset_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void AnimatedHorizontalOffset_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((e.NewValue is not double offset) || (d is not ScrollViewer scrollViewer)) return; @@ -231,9 +287,22 @@ private static IScrollInfo GetScrollInfo(ScrollViewer scrollViewer) return _propertyScrollInfoGetter(scrollViewer); } - private static bool HandlesMouseWheelScrolling(ScrollViewer scrollViewer) + private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { - return _propertyHandlesMouseWheelScrollingGetter(scrollViewer); + Debug.WriteLine($"ScrollChanged: {e.VerticalOffset} => {IsAnimationRunning}"); + + if (IsAnimationRunning) + return; + + if (Math.Abs(e.HorizontalOffset - _horizontalOffsetTarget) >= 1.0) + { + _horizontalOffsetTarget = e.HorizontalOffset; + } + + if (Math.Abs(e.VerticalOffset - _verticalOffsetTarget) >= 1.0) + { + _verticalOffsetTarget = e.VerticalOffset; + } } private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) @@ -254,26 +323,15 @@ private void Scroll(MouseWheelEventArgs e) return; } - if (!AlwaysHandleMouseWheelScrolling && !HandlesMouseWheelScrolling(_scrollViewer)) - { - return; - } - bool vertical = Keyboard.Modifiers != ModifierKeys.Shift; var tickCount = Environment.TickCount; double scrollDelta = e.Delta; - var isTouchpadScrolling = scrollDelta % Mouse.MouseWheelDeltaForOneLine != 0 || (tickCount - _lastScrollingTick < MillisecondsBetweenTouchpadScrolling && _lastScrollDelta % Mouse.MouseWheelDeltaForOneLine != 0); + var isTouchpadScrolling = scrollDelta % Mouse.MouseWheelDeltaForOneLine != 0 + || (_lastScrollWasTouchPad && (tickCount - _lastScrollingTick < MillisecondsBetweenTouchpadScrolling)); - if (isTouchpadScrolling) - { - scrollDelta *= TouchpadScrollDeltaFactor; - } - else - { - scrollDelta *= MouseScrollDeltaFactor; - } + scrollDelta *= isTouchpadScrolling ? TouchpadScrollDeltaFactor : MouseScrollDeltaFactor; if (vertical) { @@ -283,20 +341,22 @@ private void Scroll(MouseWheelEventArgs e) scrollDelta *= scrollInfo.ViewportHeight / (_scrollContentPresenter?.ActualHeight ?? _scrollViewer.ActualHeight); } - var sameDirectionAsLast = Math.Sign(e.Delta) == Math.Sign(_lastVerticalScrollingDelta); - var nowOffset = sameDirectionAsLast && _isAnimationRunning ? _verticalOffsetTarget : _scrollViewer.VerticalOffset; + Debug.WriteLine($"Delta: {scrollDelta}"); + + var nowOffset = _verticalOffsetTarget; var newOffset = Clamp(nowOffset - scrollDelta, 0, _scrollViewer.ScrollableHeight); _verticalOffsetTarget = newOffset; - if (!UseScrollingAnimation || isTouchpadScrolling) + if (isTouchpadScrolling || !UseScrollingAnimation) { - _scrollViewer.BeginAnimation(VerticalOffsetProperty, null); + _scrollViewer.BeginAnimation(AnimatedVerticalOffsetProperty, null); _scrollViewer.ScrollToVerticalOffset(newOffset); } else { - var diff = newOffset - _scrollViewer.VerticalOffset; + var from = _scrollViewer.VerticalOffset; + var diff = newOffset - from; var absDiff = Math.Abs(diff); var duration = ScrollingAnimationDuration; if (absDiff < Mouse.MouseWheelDeltaForOneLine) @@ -306,19 +366,20 @@ private void Scroll(MouseWheelEventArgs e) DoubleAnimation doubleAnimation = new DoubleAnimation() { - EasingFunction = _scrollingAnimationEase, + EasingFunction = EasingFunction, Duration = duration, - From = _scrollViewer.VerticalOffset, + From = from, To = newOffset, + Name = $"Ani{_animationIdCounter++}" }; + Debug.WriteLine($"Animate: {from} => {newOffset}, {duration.TimeSpan.TotalMilliseconds}"); + doubleAnimation.Completed += Animation_Completed; - _isAnimationRunning = true; - _scrollViewer.BeginAnimation(VerticalOffsetProperty, doubleAnimation, HandoffBehavior.SnapshotAndReplace); + _currentAnimation = doubleAnimation; + _scrollViewer.BeginAnimation(AnimatedVerticalOffsetProperty, doubleAnimation, HandoffBehavior.SnapshotAndReplace); } - - _lastVerticalScrollingDelta = e.Delta; } else { @@ -328,21 +389,19 @@ private void Scroll(MouseWheelEventArgs e) scrollDelta *= scrollInfo.ViewportWidth / (_scrollContentPresenter?.ActualWidth ?? _scrollViewer.ActualWidth); } - var sameDirectionAsLast = Math.Sign(e.Delta) == Math.Sign(_lastHorizontalScrollingDelta); - var nowOffset = sameDirectionAsLast && _isAnimationRunning ? _horizontalOffsetTarget : _scrollViewer.HorizontalOffset; + var nowOffset = _horizontalOffsetTarget; var newOffset = Clamp(nowOffset - scrollDelta, 0, _scrollViewer.ScrollableWidth); _horizontalOffsetTarget = newOffset; - _scrollViewer.BeginAnimation(HorizontalOffsetProperty, null); - - if (!UseScrollingAnimation || isTouchpadScrolling) + if (isTouchpadScrolling || !UseScrollingAnimation) { + _scrollViewer.BeginAnimation(AnimatedHorizontalOffsetProperty, null); _scrollViewer.ScrollToHorizontalOffset(newOffset); } else { - var diff = newOffset - _scrollViewer.HorizontalOffset; + var diff = newOffset - nowOffset; var absDiff = Math.Abs(diff); var duration = ScrollingAnimationDuration; if (absDiff < Mouse.MouseWheelDeltaForOneLine) @@ -352,29 +411,31 @@ private void Scroll(MouseWheelEventArgs e) DoubleAnimation doubleAnimation = new DoubleAnimation() { - EasingFunction = _scrollingAnimationEase, + EasingFunction = EasingFunction, Duration = duration, - From = _scrollViewer.HorizontalOffset, + From = nowOffset, To = newOffset, }; doubleAnimation.Completed += Animation_Completed; - _isAnimationRunning = true; - _scrollViewer.BeginAnimation(HorizontalOffsetProperty, doubleAnimation, HandoffBehavior.SnapshotAndReplace); + _currentAnimation = doubleAnimation; + _scrollViewer.BeginAnimation(AnimatedHorizontalOffsetProperty, doubleAnimation, HandoffBehavior.SnapshotAndReplace); } - - _lastHorizontalScrollingDelta = e.Delta; } _lastScrollingTick = tickCount; - _lastScrollDelta = e.Delta; + _lastScrollWasTouchPad = isTouchpadScrolling; e.Handled = true; } private void Animation_Completed(object? sender, EventArgs e) { - _isAnimationRunning = false; + if ((sender is not AnimationClock clock) || (clock.Timeline.Name != _currentAnimation?.Name)) + return; + + Debug.WriteLine("Animation finished"); + _currentAnimation = null; } } diff --git a/src/TomsToolbox.Wpf/Interactivity/AdvancedScrollWheelMode.cs b/src/TomsToolbox.Wpf/Interactivity/AdvancedScrollWheelMode.cs new file mode 100644 index 00000000..dcdc4a21 --- /dev/null +++ b/src/TomsToolbox.Wpf/Interactivity/AdvancedScrollWheelMode.cs @@ -0,0 +1,20 @@ +namespace TomsToolbox.Wpf.Interactivity; + +/// +/// Specifies the smooth scrolling mode. +/// +public enum AdvancedScrollWheelMode +{ + /// + /// Smooth scrolling is turned off. + /// + None, + /// + /// Smooth scrolling with animation. + /// + WithAnimation, + /// + /// Smooth scrolling without animation. + /// + WithoutAnimation +} diff --git a/src/TomsToolbox.Wpf/TomsToolbox.Wpf.ExternalAnnotations.xml b/src/TomsToolbox.Wpf/TomsToolbox.Wpf.ExternalAnnotations.xml index 1d3e7b61..f5e2ba2e 100644 --- a/src/TomsToolbox.Wpf/TomsToolbox.Wpf.ExternalAnnotations.xml +++ b/src/TomsToolbox.Wpf/TomsToolbox.Wpf.ExternalAnnotations.xml @@ -9,16 +9,16 @@ - - - - + + + + \ No newline at end of file