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
}
}