a2036ed5edcda61cfc717ee6f9e7d69aba3591f3
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / View / ScrollViewerPanner.cs
1 //----------------------------------------------------------------
2 // <copyright company="Microsoft Corporation">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //----------------------------------------------------------------
6
7 namespace System.Activities.Presentation.View
8 {
9     using System.Runtime;
10     using System.Windows;
11     using System.Windows.Controls;
12     using System.Windows.Input;
13     
14     internal class ScrollViewerPanner
15     {
16         private ScrollViewer scrollViewer;
17         private Point panningStartPosition;
18         private PanState currentPanState;
19         private bool inPanMode;
20         private MouseButton draggingMouseButton;        
21
22         public ScrollViewerPanner(ScrollViewer scrollViewer)
23         {
24             Fx.Assert(scrollViewer != null, "ScrollViewer should never be null");
25             this.ScrollViewer = scrollViewer;
26         }
27
28         internal enum PanState
29         {
30             Normal,     // Normal editing mode
31             ReadyToPan,
32             Panning,
33         }
34
35         public ScrollViewer ScrollViewer
36         {
37             get
38             {
39                 return this.scrollViewer;
40             }
41
42             set
43             {
44                 if (value != this.scrollViewer)
45                 {
46                     if (this.scrollViewer != null)
47                     {
48                         this.UnregisterEvents();
49                     }
50
51                     this.scrollViewer = value;
52                     if (this.scrollViewer != null)
53                     {
54                         this.RegisterEvents();
55                     }
56                 }
57             }
58         }
59
60         public bool InPanMode
61         {
62             get
63             {
64                 return this.inPanMode;
65             }
66
67             set
68             {
69                 if (this.inPanMode != value)
70                 {
71                     this.inPanMode = value;
72                     if (this.inPanMode)
73                     {
74                         this.CurrentPanState = PanState.ReadyToPan;
75                     }
76                     else
77                     {
78                         this.CurrentPanState = PanState.Normal;
79                     }
80                 }
81             }
82         }
83
84         public Cursor Hand { get; set; }
85
86         public Cursor DraggingHand { get; set; }
87
88         internal PanState CurrentPanState
89         {
90             get
91             {
92                 return this.currentPanState;
93             }
94
95             set
96             {
97                 if (this.currentPanState != value)
98                 {
99                     this.currentPanState = value;
100                     switch (this.currentPanState)
101                     {
102                         case PanState.ReadyToPan:
103                             this.scrollViewer.Cursor = this.Hand;
104                             break;
105                         case PanState.Panning:
106                             this.scrollViewer.Cursor = this.DraggingHand;
107                             break;
108                         default:
109                             this.scrollViewer.Cursor = null;
110                             break;
111                     }
112
113                     this.UpdateForceCursorProperty();
114                 }
115             }
116         }
117
118         internal bool IsInScrollableArea(Point mousePosition)
119         {
120             return mousePosition.X < this.scrollViewer.ViewportWidth && mousePosition.Y < this.scrollViewer.ViewportHeight;
121         }
122
123         internal void OnScrollViewerMouseDown(object sender, MouseButtonEventArgs e)
124         {
125             switch (this.CurrentPanState)
126             {
127                 case PanState.Normal:
128                     if (e.ChangedButton == MouseButton.Middle)
129                     {
130                         this.StartPanningIfNecessary(e);
131                     }
132
133                     break;
134                 case PanState.ReadyToPan:
135                     switch (e.ChangedButton)
136                     {
137                         case MouseButton.Left:
138                             this.StartPanningIfNecessary(e);
139                             break;
140                         case MouseButton.Middle:
141                             this.StartPanningIfNecessary(e);
142                             break;
143                         case MouseButton.Right:
144                             e.Handled = true;
145                             break;
146                     }
147
148                     break;
149                 case PanState.Panning:
150                     e.Handled = true;
151                     break;
152                 default:
153                     break;
154             }
155         }
156
157         internal void OnLostMouseCapture(object sender, MouseEventArgs e)
158         {
159             this.StopPanning();
160         }
161
162         internal void OnScrollViewerMouseMove(object sender, MouseEventArgs e)
163         {
164             switch (this.CurrentPanState)
165             {
166                 case PanState.Panning:
167                     Point currentPosition = Mouse.GetPosition(this.scrollViewer);
168                     Vector offset = Point.Subtract(currentPosition, this.panningStartPosition);
169                     this.panningStartPosition = currentPosition;
170                     this.scrollViewer.ScrollToHorizontalOffset(this.scrollViewer.HorizontalOffset - offset.X);
171                     this.scrollViewer.ScrollToVerticalOffset(this.scrollViewer.VerticalOffset - offset.Y);
172                     e.Handled = true;
173                     break;
174                 case PanState.ReadyToPan:
175                     this.UpdateForceCursorProperty();
176                     break;
177                 default:
178                     break;
179             }
180         }
181
182         internal void OnScrollViewerMouseUp(object sender, MouseButtonEventArgs e)
183         {
184             switch (this.CurrentPanState)
185             {
186                 case PanState.ReadyToPan:
187                     this.StopPanningIfNecessary(e);
188
189                     // When the mouse is captured by other windows/views, that
190                     // window/view needs this mouse-up message to release
191                     // the capture.                    
192                     if (!this.IsMouseCapturedByOthers())
193                     {
194                         e.Handled = true;
195                     }
196
197                     break;
198                 case PanState.Panning:
199                     this.StopPanningIfNecessary(e);
200                     e.Handled = true;
201                     break;
202                 default:
203                     break;
204             }
205         }
206
207         internal void OnScrollViewerKeyDown(object sender, KeyEventArgs e)
208         {
209             switch (this.CurrentPanState)
210             {
211                 // Don't change to ReadyToPan mode if the space is a 
212                 // repeated input, because repeated-key input may come 
213                 // from activity element on the scroll view.
214                 case PanState.Normal:
215                     if (e.Key == Key.Space 
216                         && !e.IsRepeat
217                         && this.AllowSwitchToPanning()) 
218                     {
219                         this.CurrentPanState = PanState.ReadyToPan;
220                     }
221
222                     break;
223                 default:
224                     break;
225             }
226         }
227
228         internal void OnScrollViewerKeyUp(object sender, KeyEventArgs e)
229         {
230             switch (this.CurrentPanState)
231             {
232                 case PanState.ReadyToPan:
233                     if (e.Key == Key.Space && !this.InPanMode)
234                     {
235                         this.CurrentPanState = PanState.Normal;
236                     }
237
238                     break;
239                 default:
240                     break;
241             }
242         }
243
244         private void StartPanningIfNecessary(MouseButtonEventArgs e)
245         {
246             if (DesignerView.IsMouseInViewport(e, this.scrollViewer))
247             {
248                 this.draggingMouseButton = e.ChangedButton;
249                 this.CurrentPanState = PanState.Panning;
250                 this.scrollViewer.Focus();
251                 this.panningStartPosition = Mouse.GetPosition(this.scrollViewer);
252                 Mouse.Capture(this.scrollViewer);
253                 e.Handled = true;
254             }
255         }
256
257         private void StopPanningIfNecessary(MouseButtonEventArgs e)
258         {
259             if (e.ChangedButton == this.draggingMouseButton && object.Equals(this.scrollViewer, Mouse.Captured))
260             {
261                 // Trigers OnLostMouseCapture
262                 this.scrollViewer.ReleaseMouseCapture();
263             }
264         }
265
266         private void StopPanning()
267         {
268             // stop panning
269             if (this.InPanMode
270                 || (Keyboard.IsKeyDown(Key.Space) && this.CurrentPanState == PanState.Panning))
271             {
272                 this.CurrentPanState = PanState.ReadyToPan;
273             }
274             else
275             {
276                 this.CurrentPanState = PanState.Normal;
277             }
278         }
279
280         private void UpdateForceCursorProperty()
281         {
282             Point pt = Mouse.GetPosition(this.ScrollViewer);
283             if (this.IsInScrollableArea(pt))
284             {
285                 this.scrollViewer.ForceCursor = true;
286             }
287             else
288             {
289                 this.scrollViewer.ForceCursor = false;
290             }
291         }
292
293         // Mouse is sometimes captured by ScrollViewer's children, like
294         // RepeatButton in Scroll Bar.
295         private bool IsMouseCapturedByOthers()
296         {
297             return (Mouse.Captured != null)
298                 && !object.Equals(Mouse.Captured, this.ScrollViewer);
299         }
300
301         private bool AllowSwitchToPanning()
302         {
303             return Mouse.LeftButton == MouseButtonState.Released
304                 && Mouse.RightButton == MouseButtonState.Released;
305         }
306
307         private void RegisterEvents()
308         {
309             this.scrollViewer.PreviewMouseDown  += new MouseButtonEventHandler(this.OnScrollViewerMouseDown);            
310             this.scrollViewer.PreviewMouseMove  += new MouseEventHandler(this.OnScrollViewerMouseMove);
311             this.scrollViewer.PreviewMouseUp    += new MouseButtonEventHandler(this.OnScrollViewerMouseUp);
312             this.scrollViewer.LostMouseCapture  += new MouseEventHandler(this.OnLostMouseCapture);
313             this.scrollViewer.KeyDown           += new KeyEventHandler(this.OnScrollViewerKeyDown);
314             this.scrollViewer.KeyUp             += new KeyEventHandler(this.OnScrollViewerKeyUp);
315         }
316
317         private void UnregisterEvents()
318         {
319             this.scrollViewer.PreviewMouseDown  -= new MouseButtonEventHandler(this.OnScrollViewerMouseDown);
320             this.scrollViewer.PreviewMouseMove  -= new MouseEventHandler(this.OnScrollViewerMouseMove);
321             this.scrollViewer.PreviewMouseUp    -= new MouseButtonEventHandler(this.OnScrollViewerMouseUp);
322             this.scrollViewer.LostMouseCapture  -= new MouseEventHandler(this.OnLostMouseCapture);
323             this.scrollViewer.KeyDown           -= new KeyEventHandler(this.OnScrollViewerKeyDown);
324             this.scrollViewer.KeyUp             -= new KeyEventHandler(this.OnScrollViewerKeyUp);
325         }
326     }
327 }