[reflection] Coop handles icalls in System.Reflection and System.RuntimeTypeHandle...
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / MiniMap / MiniMapControl.xaml.cs
1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------
4 #if DEBUG
5 //#define MINIMAP_DEBUG
6 #endif
7
8 namespace System.Activities.Presentation
9 {
10     using System;
11     using System.Windows;
12     using System.Windows.Controls;
13     using System.Windows.Input;
14     using System.Windows.Media;
15     using System.Windows.Shapes;
16     using System.Diagnostics;
17     using System.Windows.Threading;
18     using System.Globalization;
19
20     // This class is a control displaying minimap of the attached scrollableview control
21     // this class's functionality is limited to delegating events to minimap view controller
22
23     partial class MiniMapControl : UserControl
24     {
25         public static readonly DependencyProperty MapSourceProperty =
26                 DependencyProperty.Register("MapSource",
27                 typeof(ScrollViewer),
28                 typeof(MiniMapControl),
29                 new FrameworkPropertyMetadata(null,
30                 FrameworkPropertyMetadataOptions.AffectsRender,
31                 new PropertyChangedCallback(OnMapSourceChanged)));
32
33         MiniMapViewController lookupWindowManager;
34         bool isMouseDown = false;
35
36         public MiniMapControl()
37         {
38             InitializeComponent();
39             this.lookupWindowManager = new MiniMapViewController(this.lookupCanvas, this.lookupWindow, this.contentGrid);
40         }
41
42         public ScrollViewer MapSource
43         {
44             get { return GetValue(MapSourceProperty) as ScrollViewer; }
45             set { SetValue(MapSourceProperty, value); }
46         }
47
48         static void OnMapSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
49         {
50             MiniMapControl mapControl = (MiniMapControl)sender;
51             mapControl.lookupWindowManager.MapSource = mapControl.MapSource;
52         }
53
54         protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
55         {
56             base.OnMouseLeftButtonDown(e);
57             if (this.lookupWindowManager.StartMapLookupDrag(e))
58             {
59                 this.CaptureMouse();
60                 this.isMouseDown = true;
61             }
62         }
63
64         protected override void OnMouseMove(MouseEventArgs e)
65         {
66             base.OnMouseMove(e);
67             if (this.isMouseDown)
68             {
69                 this.lookupWindowManager.DoMapLookupDrag(e);
70             }
71         }
72
73         protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
74         {
75             base.OnMouseLeftButtonUp(e);
76             if (this.isMouseDown)
77             {
78                 Mouse.Capture(null);
79                 this.isMouseDown = false;
80                 this.lookupWindowManager.StopMapLookupDrag();
81             }
82         }
83
84         protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
85         {
86             this.lookupWindowManager.CenterView(e);
87             e.Handled = true;
88             base.OnMouseDoubleClick(e);
89         }
90
91         protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
92         {
93             base.OnRenderSizeChanged(sizeInfo);
94             this.lookupWindowManager.MapViewSizeChanged(sizeInfo);
95         }
96
97         // This class wraps positioning and calculating logic of the map view lookup window
98         // It is also responsible for handling mouse movements
99
100         internal class LookupWindow
101         {
102             Point mousePosition;
103             Rectangle lookupWindowRectangle;
104             MiniMapViewController parent;
105
106
107             public LookupWindow(MiniMapViewController parent, Rectangle lookupWindowRectangle)
108             {
109                 this.mousePosition = new Point();
110                 this.parent = parent;
111                 this.lookupWindowRectangle = lookupWindowRectangle;
112             }
113
114             public double Left
115             {
116                 get { return Canvas.GetLeft(this.lookupWindowRectangle); }
117                 set
118                 {
119                     //check if left corner is within minimap's range - clip if necessary
120                     double left = Math.Max(value - this.mousePosition.X, 0.0);
121                     //check if right corner is within minimap's range - clip if necessary
122                     left = (left + Width > this.parent.MapWidth ? this.parent.MapWidth - Width : left);
123                     //update canvas
124                     Canvas.SetLeft(this.lookupWindowRectangle, left);
125                 }
126             }
127
128             public double Top
129             {
130                 get { return Canvas.GetTop(this.lookupWindowRectangle); }
131                 set
132                 {
133                     //check if top corner is within minimap's range - clip if necessary
134                     double top = Math.Max(value - this.mousePosition.Y, 0.0);
135                     //check if bottom corner is within minimap's range - clip if necessary
136                     top = (top + Height > this.parent.MapHeight ? this.parent.MapHeight - Height : top);
137                     //update canvas
138                     Canvas.SetTop(this.lookupWindowRectangle, top);
139                 }
140             }
141
142             public double Width
143             {
144                 get { return this.lookupWindowRectangle.Width; }
145                 set { this.lookupWindowRectangle.Width = value; }
146             }
147
148             public double Height
149             {
150                 get { return this.lookupWindowRectangle.Height; }
151                 set { this.lookupWindowRectangle.Height = value; }
152             }
153
154             public double MapCenterXPoint
155             {
156                 get { return this.Left + (this.Width / 2.0); }
157             }
158
159             public double MapCenterYPoint
160             {
161                 get { return this.Top + (this.Height / 2.0); }
162             }
163
164             public double MousePositionX
165             {
166                 get { return this.mousePosition.X; }
167             }
168
169             public double MousePositionY
170             {
171                 get { return this.mousePosition.Y; }
172             }
173
174             public bool IsSelected
175             {
176                 get;
177                 private set;
178             }
179
180             public void SetPosition(double left, double top)
181             {
182                 Left = left;
183                 Top = top;
184             }
185
186             public void SetSize(double width, double height)
187             {
188                 Width = width;
189                 Height = height;
190             }
191
192             //whenever user clicks on the minimap, i check if clicked object is 
193             //a lookup window - if yes - i store mouse offset within the window
194             //and mark it as selected
195             public bool Select(object clickedItem, Point clickedPosition)
196             {
197                 if (clickedItem == this.lookupWindowRectangle)
198                 {
199                     this.mousePosition = clickedPosition;
200                     this.IsSelected = true;
201                 }
202                 else
203                 {
204                     Unselect();
205                 }
206                 return this.IsSelected;
207             }
208
209             public void Unselect()
210             {
211                 this.mousePosition.X = 0;
212                 this.mousePosition.Y = 0;
213                 this.IsSelected = false;
214             }
215
216             public void Center(double x, double y)
217             {
218                 Left = x - (Width / 2.0);
219                 Top = y - (Height / 2.0);
220             }
221
222             public void Refresh(bool unselect)
223             {
224                 if (unselect)
225                 {
226                     Unselect();
227                 }
228                 SetPosition(Left, Top);
229             }
230         }
231
232         // This class is responsible for calculating size of the minimap's view area, as well as
233         // maintaining the bi directional link between minimap and control beeing visualized.
234         // Whenever minimap's view window position is updated, the control's content is scrolled 
235         // to calculated position
236         // Whenever control's content is resized or scrolled, minimap reflects that change in 
237         // recalculating view's window size and/or position
238
239         internal class MiniMapViewController
240         {
241             Canvas lookupCanvas;
242             Grid contentGrid;
243             ScrollViewer mapSource;
244             LookupWindow lookupWindow;
245
246             public MiniMapViewController(Canvas lookupCanvas, Rectangle lookupWindowRectangle, Grid contentGrid)
247             {
248                 this.lookupWindow = new LookupWindow(this, lookupWindowRectangle);
249                 this.lookupCanvas = lookupCanvas;
250                 this.contentGrid = contentGrid;
251             }
252
253             public ScrollViewer MapSource
254             {
255                 get { return this.mapSource; }
256                 set
257                 {
258                     this.mapSource = value;
259                     //calculate view's size and set initial position
260                     this.lookupWindow.Unselect();
261                     this.CalculateLookupWindowSize();
262                     this.lookupWindow.SetPosition(0.0, 0.0);
263                     CalculateMapPosition(this.lookupWindow.Left, this.lookupWindow.Top);
264                     this.UpdateContentGrid();
265
266                     if (null != this.mapSource && null != this.mapSource.Content && this.mapSource.Content is FrameworkElement)
267                     {
268                         FrameworkElement content = (FrameworkElement)this.mapSource.Content;
269                         //hook up for all content size changes - handle them in OnContentSizeChanged method
270                         content.SizeChanged += (s, e) =>
271                             {
272                                 this.contentGrid.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
273                                     new Action(() => { OnContentSizeChanged(s, e); }));
274                             };
275
276                         //in case of scroll viewer - there are two different events to handle in one notification:
277                         this.mapSource.ScrollChanged += (s, e) =>
278                         {
279                             this.contentGrid.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
280                                 new Action(() =>
281                                     {
282                                         //when user changes scroll position - delegate it to OnMapSourceScrollChange
283                                         if (0.0 != e.HorizontalChange || 0.0 != e.VerticalChange)
284                                         {
285                                             OnMapSourceScrollChanged(s, e);
286                                         }
287                                         //when size of the scroll changes delegate it to OnContentSizeChanged
288                                         if (0.0 != e.ViewportWidthChange || 0.0 != e.ViewportHeightChange)
289                                         {
290                                             OnContentSizeChanged(s, e);
291                                         }
292                                     }));
293                         };
294                         this.OnMapSourceScrollChanged(this, null);
295                         this.OnContentSizeChanged(this, null);
296                     }
297                 }
298             }
299
300             //bunch of helper getters - used to increase algorithm readability and provide default
301             //values, always valid values, so no additional divide-by-zero checks are neccessary
302
303             public double MapWidth
304             {
305                 get { return this.contentGrid.ActualWidth - 2 * (this.contentGrid.ColumnDefinitions[0].MinWidth); }
306             }
307
308             public double MapHeight
309             {
310                 get { return this.contentGrid.ActualHeight - 2 * (this.contentGrid.RowDefinitions[0].MinHeight); }
311             }
312
313             internal LookupWindow LookupWindow
314             {
315                 get { return this.lookupWindow; }
316             }
317
318             double VisibleSourceWidth
319             {
320                 get { return (null == MapSource || 0.0 == MapSource.ViewportWidth ? 1.0 : MapSource.ViewportWidth); }
321             }
322
323             double VisibleSourceHeight
324             {
325                 get { return (null == MapSource || 0.0 == MapSource.ViewportHeight ? 1.0 : MapSource.ViewportHeight); }
326             }
327
328
329             public void CenterView(MouseEventArgs args)
330             {
331                 Point pt = args.GetPosition(this.lookupCanvas);
332                 this.lookupWindow.Unselect();
333                 this.lookupWindow.Center(pt.X, pt.Y);
334                 CalculateMapPosition(this.lookupWindow.Left, this.lookupWindow.Top);
335             }
336
337             public void MapViewSizeChanged(SizeChangedInfo sizeInfo)
338             {
339                 this.OnContentSizeChanged(this, EventArgs.Empty);
340                 this.lookupWindow.Unselect();
341                 this.CalculateLookupWindowSize();
342                 if (sizeInfo.WidthChanged && 0.0 != sizeInfo.PreviousSize.Width)
343                 {
344                     this.lookupWindow.Left =
345                         this.lookupWindow.Left * (sizeInfo.NewSize.Width / sizeInfo.PreviousSize.Width);
346                 }
347                 if (sizeInfo.HeightChanged && 0.0 != sizeInfo.PreviousSize.Height)
348                 {
349                     this.lookupWindow.Top =
350                         this.lookupWindow.Top * (sizeInfo.NewSize.Height / sizeInfo.PreviousSize.Height);
351                 }
352             }
353
354             public bool StartMapLookupDrag(MouseEventArgs args)
355             {
356                 bool result = false;
357                 HitTestResult hitTest =
358                     VisualTreeHelper.HitTest(this.lookupCanvas, args.GetPosition(this.lookupCanvas));
359
360                 if (null != hitTest && null != hitTest.VisualHit)
361                 {
362                     Point clickedPosition = args.GetPosition(hitTest.VisualHit as IInputElement);
363                     result = this.lookupWindow.Select(hitTest.VisualHit, clickedPosition);
364                 }
365                 return result;
366             }
367
368             public void StopMapLookupDrag()
369             {
370                 this.lookupWindow.Unselect();
371             }
372
373             public void DoMapLookupDrag(MouseEventArgs args)
374             {
375                 if (args.LeftButton == MouseButtonState.Released && this.lookupWindow.IsSelected)
376                 {
377                     this.lookupWindow.Unselect();
378                 }
379                 if (this.lookupWindow.IsSelected)
380                 {
381                     Point to = args.GetPosition(this.lookupCanvas);
382                     this.lookupWindow.SetPosition(to.X, to.Y);
383                     CalculateMapPosition(
384                         to.X - this.lookupWindow.MousePositionX,
385                         to.Y - this.lookupWindow.MousePositionY);
386                 }
387             }
388
389             void CalculateMapPosition(double left, double top)
390             {
391                 if (null != MapSource && 0 != this.lookupWindow.Width && 0 != this.lookupWindow.Height)
392                 {
393                     MapSource.ScrollToHorizontalOffset((left / this.lookupWindow.Width) * VisibleSourceWidth);
394                     MapSource.ScrollToVerticalOffset((top / this.lookupWindow.Height) * VisibleSourceHeight);
395                 }
396             }
397
398             //this method calculates position of the lookup window on the minimap - it should be triggered when:
399             // - user modifies a scroll position by draggin a scroll bar
400             // - scroll sizes are updated by change of the srcollviewer size
401             // - user drags minimap view - however, in this case no lookup update takes place
402             void OnMapSourceScrollChanged(object sender, ScrollChangedEventArgs e)
403             {
404                 if (!this.lookupWindow.IsSelected && null != MapSource)
405                 {
406                     this.lookupWindow.Unselect();
407                     this.lookupWindow.Left =
408                         this.lookupWindow.Width * (MapSource.HorizontalOffset / VisibleSourceWidth);
409
410                     this.lookupWindow.Top =
411                         this.lookupWindow.Height * (MapSource.VerticalOffset / VisibleSourceHeight);
412                 }
413                 DumpData("OnMapSourceScrollChange");
414             }
415
416             //this method calculates size and position of the minimap view - it should be triggered when:
417             // - zoom changes
418             // - visible size of the scrollviewer (which is map source) changes
419             // - visible size of the minimap control changes 
420             void OnContentSizeChanged(object sender, EventArgs e)
421             {
422                 //get old center point coordinates
423                 double centerX = this.lookupWindow.MapCenterXPoint;
424                 double centeryY = this.lookupWindow.MapCenterYPoint;
425                 //update the minimap itself
426                 this.UpdateContentGrid();
427                 //calculate new size
428                 this.CalculateLookupWindowSize();
429                 //try to center around old center points (window may be moved if doesn't fit)
430                 this.lookupWindow.Center(centerX, centeryY);
431                 DumpData("OnContentSizeChanged");
432             }
433
434             //this method calculates size of the lookup rectangle, based on the visible size of the object, 
435             //including current map width
436             void CalculateLookupWindowSize()
437             {
438                 double width = this.MapWidth;
439                 double height = this.MapHeight;
440
441                 if (this.MapSource.ScrollableWidth != 0 && this.MapSource.ExtentWidth != 0)
442                 {
443                     width = (this.MapSource.ViewportWidth / this.MapSource.ExtentWidth) * this.MapWidth;
444                 }
445                 else
446                 {
447                     //width = 
448                 }
449                 if (this.MapSource.ScrollableHeight != 0 && this.MapSource.ExtentHeight != 0)
450                 {
451                     height = (this.MapSource.ViewportHeight / this.MapSource.ExtentHeight) * this.MapHeight;
452                 }
453                 this.lookupWindow.SetSize(width, height);
454             }
455
456             //this method updates content grid of the minimap - most likely, minimap view will be scaled to fit
457             //the window - so there will be some extra space visible on the left and right sides or above and below actual
458             //mini map view - we don't want lookup rectangle to navigate within that area, since it is not representing
459             //actual view - we increase margins of the minimap to disallow this
460             void UpdateContentGrid()
461             {
462                 bool resetToDefault = true;
463                 if (this.MapSource.ExtentWidth != 0 && this.MapSource.ExtentHeight != 0)
464                 {
465                     //get width to height ratio from map source - we want to display our minimap in the same ratio
466                     double widthToHeightRatio = this.MapSource.ExtentWidth / this.MapSource.ExtentHeight;
467
468                     //calculate current width to height ratio on the minimap
469                     double height = this.contentGrid.ActualHeight;
470                     double width = this.contentGrid.ActualWidth;
471                     //ideally - it should be 1 - whole view perfectly fits minimap 
472                     double minimapWidthToHeightRatio = (height * widthToHeightRatio) / (width > 1.0 ? width : 1.0);
473
474                     //if value is greater than one - we have to reduce height
475                     if (minimapWidthToHeightRatio > 1.0)
476                     {
477                         double margin = (height - (height / minimapWidthToHeightRatio)) / 2.0;
478
479                         this.contentGrid.ColumnDefinitions[0].MinWidth = 0.0;
480                         this.contentGrid.ColumnDefinitions[2].MinWidth = 0.0;
481                         this.contentGrid.RowDefinitions[0].MinHeight = margin;
482                         this.contentGrid.RowDefinitions[2].MinHeight = margin;
483                         resetToDefault = false;
484                     }
485                     //if value is less than one - we have to reduce width
486                     else if (minimapWidthToHeightRatio < 1.0)
487                     {
488                         double margin = (width - (width * minimapWidthToHeightRatio)) / 2.0;
489                         this.contentGrid.ColumnDefinitions[0].MinWidth = margin;
490                         this.contentGrid.ColumnDefinitions[2].MinWidth = margin;
491                         this.contentGrid.RowDefinitions[0].MinHeight = 0.0;
492                         this.contentGrid.RowDefinitions[2].MinHeight = 0.0;
493                         resetToDefault = false;
494                     }
495                 }
496                 //perfect match or nothing to display - no need to setup margins
497                 if (resetToDefault)
498                 {
499                     this.contentGrid.ColumnDefinitions[0].MinWidth = 0.0;
500                     this.contentGrid.ColumnDefinitions[2].MinWidth = 0.0;
501                     this.contentGrid.RowDefinitions[0].MinHeight = 0.0;
502                     this.contentGrid.RowDefinitions[2].MinHeight = 0.0;
503                 }
504             }
505
506             [Conditional("MINIMAP_DEBUG")]
507             void DumpData(string prefix)
508             {
509                 System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} ScrollViewer: EWidth {1}, EHeight {2}, AWidth {3}, AHeight {4}, ViewPortW {5} ViewPortH {6}", prefix, mapSource.ExtentWidth, mapSource.ExtentHeight, mapSource.ActualWidth, mapSource.ActualHeight, mapSource.ViewportWidth, mapSource.ViewportHeight));
510             }
511
512         }
513     }
514 }