using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Threading; namespace ScrollableArea { public class KineticBehavior { #region Friction /// /// Friction Attached Dependency Property /// public static readonly DependencyProperty FrictionProperty = DependencyProperty.RegisterAttached("Friction", typeof(double), typeof(KineticBehavior), new FrameworkPropertyMetadata(0.95)); /// /// Gets the Friction property. This dependency property /// indicates .... /// public static double GetFriction(DependencyObject d) { return (double)d.GetValue(FrictionProperty); } /// /// Sets the Friction property. This dependency property /// indicates .... /// public static void SetFriction(DependencyObject d, double value) { d.SetValue(FrictionProperty, value); } #endregion #region ScrollStartPoint /// /// ScrollStartPoint Attached Dependency Property /// private static readonly DependencyProperty ScrollStartPointProperty = DependencyProperty.RegisterAttached("ScrollStartPoint", typeof(Point), typeof(KineticBehavior), new FrameworkPropertyMetadata(new Point())); /// /// Gets the ScrollStartPoint property. This dependency property /// indicates .... /// private static Point GetScrollStartPoint(DependencyObject d) { return (Point)d.GetValue(ScrollStartPointProperty); } /// /// Sets the ScrollStartPoint property. This dependency property /// indicates .... /// private static void SetScrollStartPoint(DependencyObject d, Point value) { d.SetValue(ScrollStartPointProperty, value); } #endregion #region ScrollStartOffset /// /// ScrollStartOffset Attached Dependency Property /// private static readonly DependencyProperty ScrollStartOffsetProperty = DependencyProperty.RegisterAttached("ScrollStartOffset", typeof(Point), typeof(KineticBehavior), new FrameworkPropertyMetadata(new Point())); /// /// Gets the ScrollStartOffset property. This dependency property /// indicates .... /// private static Point GetScrollStartOffset(DependencyObject d) { return (Point)d.GetValue(ScrollStartOffsetProperty); } /// /// Sets the ScrollStartOffset property. This dependency property /// indicates .... /// private static void SetScrollStartOffset(DependencyObject d, Point value) { d.SetValue(ScrollStartOffsetProperty, value); } #endregion #region InertiaProcessor /// /// InertiaProcessor Attached Dependency Property /// private static readonly DependencyProperty InertiaProcessorProperty = DependencyProperty.RegisterAttached("InertiaProcessor", typeof(InertiaHandler), typeof(KineticBehavior), new FrameworkPropertyMetadata((InertiaHandler)null)); /// /// Gets the InertiaProcessor property. This dependency property /// indicates .... /// private static InertiaHandler GetInertiaProcessor(DependencyObject d) { return (InertiaHandler)d.GetValue(InertiaProcessorProperty); } /// /// Sets the InertiaProcessor property. This dependency property /// indicates .... /// private static void SetInertiaProcessor(DependencyObject d, InertiaHandler value) { d.SetValue(InertiaProcessorProperty, value); } #endregion #region HandleKineticScrolling /// /// HandleKineticScrolling Attached Dependency Property /// public static readonly DependencyProperty HandleKineticScrollingProperty = DependencyProperty.RegisterAttached("HandleKineticScrolling", typeof(bool), typeof(KineticBehavior), new FrameworkPropertyMetadata(false, OnHandleKineticScrollingChanged)); /// /// Gets the HandleKineticScrolling property. This dependency property /// indicates .... /// public static bool GetHandleKineticScrolling(DependencyObject d) { return (bool)d.GetValue(HandleKineticScrollingProperty); } /// /// Sets the HandleKineticScrolling property. This dependency property /// indicates .... /// public static void SetHandleKineticScrolling(DependencyObject d, bool value) { d.SetValue(HandleKineticScrollingProperty, value); } /// /// Handles changes to the HandleKineticScrolling property. /// private static void OnHandleKineticScrollingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var scroller = d as ScrollViewer; if ((bool) e.NewValue) { scroller.PreviewMouseDown += OnPreviewMouseDown; scroller.PreviewMouseMove += OnPreviewMouseMove; scroller.PreviewMouseUp += OnPreviewMouseUp; SetInertiaProcessor(scroller, new InertiaHandler(scroller)); } else { scroller.PreviewMouseDown -= OnPreviewMouseDown; scroller.PreviewMouseMove -= OnPreviewMouseMove; scroller.PreviewMouseUp -= OnPreviewMouseUp; var inertia = GetInertiaProcessor(scroller); inertia?.Dispose(); } } #endregion #region Mouse Events private static void OnPreviewMouseDown(object sender, MouseButtonEventArgs e) { var scrollViewer = (ScrollViewer)sender; if (e.ChangedButton == MouseButton.Left && scrollViewer.IsMouseOver && !(e.Source is Dominion.NET_WPF.Controls.CardStackControl) && (e.OriginalSource is ScrollViewer || e.OriginalSource is TextBlock || e.OriginalSource is DockPanel || e.OriginalSource is Image || e.OriginalSource is Label )) { // Save starting point, used later when determining how much to scroll. SetScrollStartPoint(scrollViewer, e.GetPosition(scrollViewer)); SetScrollStartOffset(scrollViewer, new Point(scrollViewer.HorizontalOffset, scrollViewer.VerticalOffset)); scrollViewer.Cursor = Cursors.ScrollAll; scrollViewer.CaptureMouse(); } } private static void OnPreviewMouseMove(object sender, MouseEventArgs e) { var scrollViewer = (ScrollViewer)sender; if (scrollViewer.IsMouseCaptured) { var currentPoint = e.GetPosition(scrollViewer); var scrollStartPoint = GetScrollStartPoint(scrollViewer); // Determine the new amount to scroll. var delta = new Point(scrollStartPoint.X - currentPoint.X, scrollStartPoint.Y - currentPoint.Y); var scrollStartOffset = GetScrollStartOffset(scrollViewer); var scrollTarget = new Point(scrollStartOffset.X + delta.X, scrollStartOffset.Y + delta.Y); var inertiaProcessor = GetInertiaProcessor(scrollViewer); if (inertiaProcessor != null) inertiaProcessor.ScrollTarget = scrollTarget; // Scroll to the new position. scrollViewer.ScrollToHorizontalOffset(scrollTarget.X); scrollViewer.ScrollToVerticalOffset(scrollTarget.Y); } } private static void OnPreviewMouseUp(object sender, MouseButtonEventArgs e) { var scrollViewer = (ScrollViewer)sender; if (scrollViewer.IsMouseCaptured) { scrollViewer.Cursor = null; scrollViewer.ReleaseMouseCapture(); } } #endregion #region Inertia Stuff /// /// Handles the inertia /// private class InertiaHandler : IDisposable { private Point _previousPoint; private Vector _velocity; private readonly ScrollViewer _scroller; private readonly DispatcherTimer _animationTimer; private Point _scrollTarget; public Point ScrollTarget { private get { return _scrollTarget; } set { _scrollTarget = value; } } public InertiaHandler(ScrollViewer scroller) { _scroller = scroller; _animationTimer = new DispatcherTimer {Interval = new TimeSpan(0, 0, 0, 0, 20)}; _animationTimer.Tick += HandleWorldTimerTick; _animationTimer.Start(); } private void HandleWorldTimerTick(object sender, EventArgs e) { if (_scroller.IsMouseCaptured) { var currentPoint = Mouse.GetPosition(_scroller); _velocity = _previousPoint - currentPoint; _previousPoint = currentPoint; } else { if (_velocity.Length > 1) { _scroller.ScrollToHorizontalOffset(ScrollTarget.X); _scroller.ScrollToVerticalOffset(ScrollTarget.Y); _scrollTarget.X += _velocity.X; _scrollTarget.Y += _velocity.Y; _velocity *= GetFriction(_scroller); } } } #region IDisposable Members public void Dispose() { _animationTimer.Stop(); } #endregion } #endregion } }