File: cdf\src\NetFx40\Tools\System.Activities.Presentation\System\Activities\Presentation\View\ScrollViewerPanner.cs
Project: ndp\System.Data.csproj (System.Data)
//----------------------------------------------------------------
// <copyright company="Microsoft Corporation">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//----------------------------------------------------------------
 
namespace System.Activities.Presentation.View
{
    using System.Runtime;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    
    internal class ScrollViewerPanner
    {
        private ScrollViewer scrollViewer;
        private Point panningStartPosition;
        private PanState currentPanState;
        private bool inPanMode;
        private MouseButton draggingMouseButton;        
 
        public ScrollViewerPanner(ScrollViewer scrollViewer)
        {
            Fx.Assert(scrollViewer != null, "ScrollViewer should never be null");
            this.ScrollViewer = scrollViewer;
        }
 
        internal enum PanState
        {
            Normal,     // Normal editing mode
            ReadyToPan,
            Panning,
        }
 
        public ScrollViewer ScrollViewer
        {
            get
            {
                return this.scrollViewer;
            }
 
            set
            {
                if (value != this.scrollViewer)
                {
                    if (this.scrollViewer != null)
                    {
                        this.UnregisterEvents();
                    }
 
                    this.scrollViewer = value;
                    if (this.scrollViewer != null)
                    {
                        this.RegisterEvents();
                    }
                }
            }
        }
 
        public bool InPanMode
        {
            get
            {
                return this.inPanMode;
            }
 
            set
            {
                if (this.inPanMode != value)
                {
                    this.inPanMode = value;
                    if (this.inPanMode)
                    {
                        this.CurrentPanState = PanState.ReadyToPan;
                    }
                    else
                    {
                        this.CurrentPanState = PanState.Normal;
                    }
                }
            }
        }
 
        public Cursor Hand { get; set; }
 
        public Cursor DraggingHand { get; set; }
 
        internal PanState CurrentPanState
        {
            get
            {
                return this.currentPanState;
            }
 
            set
            {
                if (this.currentPanState != value)
                {
                    this.currentPanState = value;
                    switch (this.currentPanState)
                    {
                        case PanState.ReadyToPan:
                            this.scrollViewer.Cursor = this.Hand;
                            break;
                        case PanState.Panning:
                            this.scrollViewer.Cursor = this.DraggingHand;
                            break;
                        default:
                            this.scrollViewer.Cursor = null;
                            break;
                    }
 
                    this.UpdateForceCursorProperty();
                }
            }
        }
 
        internal bool IsInScrollableArea(Point mousePosition)
        {
            return mousePosition.X < this.scrollViewer.ViewportWidth && mousePosition.Y < this.scrollViewer.ViewportHeight;
        }
 
        internal void OnScrollViewerMouseDown(object sender, MouseButtonEventArgs e)
        {
            switch (this.CurrentPanState)
            {
                case PanState.Normal:
                    if (e.ChangedButton == MouseButton.Middle)
                    {
                        this.StartPanningIfNecessary(e);
                    }
 
                    break;
                case PanState.ReadyToPan:
                    switch (e.ChangedButton)
                    {
                        case MouseButton.Left:
                            this.StartPanningIfNecessary(e);
                            break;
                        case MouseButton.Middle:
                            this.StartPanningIfNecessary(e);
                            break;
                        case MouseButton.Right:
                            e.Handled = true;
                            break;
                    }
 
                    break;
                case PanState.Panning:
                    e.Handled = true;
                    break;
                default:
                    break;
            }
        }
 
        internal void OnLostMouseCapture(object sender, MouseEventArgs e)
        {
            this.StopPanning();
        }
 
        internal void OnScrollViewerMouseMove(object sender, MouseEventArgs e)
        {
            switch (this.CurrentPanState)
            {
                case PanState.Panning:
                    Point currentPosition = Mouse.GetPosition(this.scrollViewer);
                    Vector offset = Point.Subtract(currentPosition, this.panningStartPosition);
                    this.panningStartPosition = currentPosition;
                    this.scrollViewer.ScrollToHorizontalOffset(this.scrollViewer.HorizontalOffset - offset.X);
                    this.scrollViewer.ScrollToVerticalOffset(this.scrollViewer.VerticalOffset - offset.Y);
                    e.Handled = true;
                    break;
                case PanState.ReadyToPan:
                    this.UpdateForceCursorProperty();
                    break;
                default:
                    break;
            }
        }
 
        internal void OnScrollViewerMouseUp(object sender, MouseButtonEventArgs e)
        {
            switch (this.CurrentPanState)
            {
                case PanState.ReadyToPan:
                    this.StopPanningIfNecessary(e);
 
                    // When the mouse is captured by other windows/views, that
                    // window/view needs this mouse-up message to release
                    // the capture.                    
                    if (!this.IsMouseCapturedByOthers())
                    {
                        e.Handled = true;
                    }
 
                    break;
                case PanState.Panning:
                    this.StopPanningIfNecessary(e);
                    e.Handled = true;
                    break;
                default:
                    break;
            }
        }
 
        internal void OnScrollViewerKeyDown(object sender, KeyEventArgs e)
        {
            switch (this.CurrentPanState)
            {
                // Don't change to ReadyToPan mode if the space is a 
                // repeated input, because repeated-key input may come 
                // from activity element on the scroll view.
                case PanState.Normal:
                    if (e.Key == Key.Space 
                        && !e.IsRepeat
                        && this.AllowSwitchToPanning()) 
                    {
                        this.CurrentPanState = PanState.ReadyToPan;
                    }
 
                    break;
                default:
                    break;
            }
        }
 
        internal void OnScrollViewerKeyUp(object sender, KeyEventArgs e)
        {
            switch (this.CurrentPanState)
            {
                case PanState.ReadyToPan:
                    if (e.Key == Key.Space && !this.InPanMode)
                    {
                        this.CurrentPanState = PanState.Normal;
                    }
 
                    break;
                default:
                    break;
            }
        }
 
        private void StartPanningIfNecessary(MouseButtonEventArgs e)
        {
            if (DesignerView.IsMouseInViewport(e, this.scrollViewer))
            {
                this.draggingMouseButton = e.ChangedButton;
                this.CurrentPanState = PanState.Panning;
                this.scrollViewer.Focus();
                this.panningStartPosition = Mouse.GetPosition(this.scrollViewer);
                Mouse.Capture(this.scrollViewer);
                e.Handled = true;
            }
        }
 
        private void StopPanningIfNecessary(MouseButtonEventArgs e)
        {
            if (e.ChangedButton == this.draggingMouseButton && object.Equals(this.scrollViewer, Mouse.Captured))
            {
                // Trigers OnLostMouseCapture
                this.scrollViewer.ReleaseMouseCapture();
            }
        }
 
        private void StopPanning()
        {
            // stop panning
            if (this.InPanMode
                || (Keyboard.IsKeyDown(Key.Space) && this.CurrentPanState == PanState.Panning))
            {
                this.CurrentPanState = PanState.ReadyToPan;
            }
            else
            {
                this.CurrentPanState = PanState.Normal;
            }
        }
 
        private void UpdateForceCursorProperty()
        {
            Point pt = Mouse.GetPosition(this.ScrollViewer);
            if (this.IsInScrollableArea(pt))
            {
                this.scrollViewer.ForceCursor = true;
            }
            else
            {
                this.scrollViewer.ForceCursor = false;
            }
        }
 
        // Mouse is sometimes captured by ScrollViewer's children, like
        // RepeatButton in Scroll Bar.
        private bool IsMouseCapturedByOthers()
        {
            return (Mouse.Captured != null)
                && !object.Equals(Mouse.Captured, this.ScrollViewer);
        }
 
        private bool AllowSwitchToPanning()
        {
            return Mouse.LeftButton == MouseButtonState.Released
                && Mouse.RightButton == MouseButtonState.Released;
        }
 
        private void RegisterEvents()
        {
            this.scrollViewer.PreviewMouseDown  += new MouseButtonEventHandler(this.OnScrollViewerMouseDown);            
            this.scrollViewer.PreviewMouseMove  += new MouseEventHandler(this.OnScrollViewerMouseMove);
            this.scrollViewer.PreviewMouseUp    += new MouseButtonEventHandler(this.OnScrollViewerMouseUp);
            this.scrollViewer.LostMouseCapture  += new MouseEventHandler(this.OnLostMouseCapture);
            this.scrollViewer.KeyDown           += new KeyEventHandler(this.OnScrollViewerKeyDown);
            this.scrollViewer.KeyUp             += new KeyEventHandler(this.OnScrollViewerKeyUp);
        }
 
        private void UnregisterEvents()
        {
            this.scrollViewer.PreviewMouseDown  -= new MouseButtonEventHandler(this.OnScrollViewerMouseDown);
            this.scrollViewer.PreviewMouseMove  -= new MouseEventHandler(this.OnScrollViewerMouseMove);
            this.scrollViewer.PreviewMouseUp    -= new MouseButtonEventHandler(this.OnScrollViewerMouseUp);
            this.scrollViewer.LostMouseCapture  -= new MouseEventHandler(this.OnLostMouseCapture);
            this.scrollViewer.KeyDown           -= new KeyEventHandler(this.OnScrollViewerKeyDown);
            this.scrollViewer.KeyUp             -= new KeyEventHandler(this.OnScrollViewerKeyUp);
        }
    }
}