Initial Commit
This commit is contained in:
406
Charts/Navigation/MouseNavigation.cs
Normal file
406
Charts/Navigation/MouseNavigation.cs
Normal file
@@ -0,0 +1,406 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Diagnostics;
|
||||
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Provides common methods of mouse navigation around viewport</summary>
|
||||
public class MouseNavigation : NavigationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MouseNavigation"/> class.
|
||||
/// </summary>
|
||||
public MouseNavigation() { }
|
||||
|
||||
private AdornerLayer adornerLayer;
|
||||
protected AdornerLayer AdornerLayer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (adornerLayer == null)
|
||||
{
|
||||
adornerLayer = AdornerLayer.GetAdornerLayer(this);
|
||||
if (adornerLayer != null)
|
||||
{
|
||||
adornerLayer.IsHitTestVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
return adornerLayer;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
base.OnPlotterAttached(plotter);
|
||||
|
||||
Mouse.AddMouseDownHandler(Parent, OnMouseDown);
|
||||
Mouse.AddMouseMoveHandler(Parent, OnMouseMove);
|
||||
Mouse.AddMouseUpHandler(Parent, OnMouseUp);
|
||||
Mouse.AddMouseWheelHandler(Parent, OnMouseWheel);
|
||||
|
||||
plotter.KeyDown += new KeyEventHandler(OnParentKeyDown);
|
||||
}
|
||||
|
||||
protected override void OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
plotter.KeyDown -= new KeyEventHandler(OnParentKeyDown);
|
||||
|
||||
Mouse.RemoveMouseDownHandler(Parent, OnMouseDown);
|
||||
Mouse.RemoveMouseMoveHandler(Parent, OnMouseMove);
|
||||
Mouse.RemoveMouseUpHandler(Parent, OnMouseUp);
|
||||
Mouse.RemoveMouseWheelHandler(Parent, OnMouseWheel);
|
||||
|
||||
base.OnPlotterDetaching(plotter);
|
||||
}
|
||||
|
||||
private void OnParentKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape || e.Key == Key.Back)
|
||||
{
|
||||
if (isZooming)
|
||||
{
|
||||
isZooming = false;
|
||||
zoomRect = null;
|
||||
ReleaseMouseCapture();
|
||||
RemoveSelectionAdorner();
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (!e.Handled)
|
||||
{
|
||||
Point mousePos = e.GetPosition(this);
|
||||
int delta = -e.Delta;
|
||||
MouseWheelZoom(mousePos, delta);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override string ToString()
|
||||
{
|
||||
if (!String.IsNullOrEmpty(Name))
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
return base.ToString();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool adornerAdded;
|
||||
RectangleSelectionAdorner selectionAdorner;
|
||||
private void AddSelectionAdorner()
|
||||
{
|
||||
if (!adornerAdded)
|
||||
{
|
||||
AdornerLayer layer = AdornerLayer;
|
||||
if (layer != null)
|
||||
{
|
||||
selectionAdorner = new RectangleSelectionAdorner(this) { Border = zoomRect };
|
||||
|
||||
layer.Add(selectionAdorner);
|
||||
adornerAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSelectionAdorner()
|
||||
{
|
||||
AdornerLayer layer = AdornerLayer;
|
||||
if (layer != null)
|
||||
{
|
||||
layer.Remove(selectionAdorner);
|
||||
adornerAdded = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelectionAdorner()
|
||||
{
|
||||
selectionAdorner.Border = zoomRect;
|
||||
selectionAdorner.InvalidateVisual();
|
||||
}
|
||||
|
||||
Rect? zoomRect = null;
|
||||
private const double wheelZoomSpeed = 1.2;
|
||||
private bool shouldKeepRatioWhileZooming;
|
||||
|
||||
private bool isZooming = false;
|
||||
protected bool IsZooming
|
||||
{
|
||||
get { return isZooming; }
|
||||
}
|
||||
|
||||
private bool isPanning = false;
|
||||
protected bool IsPanning
|
||||
{
|
||||
get { return isPanning; }
|
||||
}
|
||||
|
||||
private Point panningStartPointInViewport;
|
||||
protected Point PanningStartPointInViewport
|
||||
{
|
||||
get { return panningStartPointInViewport; }
|
||||
}
|
||||
|
||||
private Point zoomStartPoint;
|
||||
|
||||
private static bool IsShiftOrCtrl
|
||||
{
|
||||
get
|
||||
{
|
||||
ModifierKeys currKeys = Keyboard.Modifiers;
|
||||
return (currKeys | ModifierKeys.Shift) == currKeys ||
|
||||
(currKeys | ModifierKeys.Control) == currKeys;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool ShouldStartPanning(MouseButtonEventArgs e)
|
||||
{
|
||||
return e.ChangedButton == MouseButton.Left && Keyboard.Modifiers == ModifierKeys.None;
|
||||
}
|
||||
|
||||
protected virtual bool ShouldStartZoom(MouseButtonEventArgs e)
|
||||
{
|
||||
return e.ChangedButton == MouseButton.Left && IsShiftOrCtrl;
|
||||
}
|
||||
|
||||
Point panningStartPointInScreen;
|
||||
protected virtual void StartPanning(MouseButtonEventArgs e)
|
||||
{
|
||||
panningStartPointInScreen = e.GetPosition(this);
|
||||
panningStartPointInViewport = panningStartPointInScreen.ScreenToViewport(Viewport.Transform);
|
||||
|
||||
Plotter2D.UndoProvider.CaptureOldValue(Viewport, Viewport2D.VisibleProperty, Viewport.Visible);
|
||||
|
||||
isPanning = true;
|
||||
|
||||
// not capturing mouse because this made some tools like PointSelector not
|
||||
// receive MouseUp events on markers;
|
||||
// Mouse will be captured later, in the first MouseMove handler call.
|
||||
// CaptureMouse();
|
||||
|
||||
Viewport.PanningState = Viewport2DPanningState.Panning;
|
||||
|
||||
//e.Handled = true;
|
||||
}
|
||||
|
||||
protected virtual void StartZoom(MouseButtonEventArgs e)
|
||||
{
|
||||
zoomStartPoint = e.GetPosition(this);
|
||||
if (Viewport.Output.Contains(zoomStartPoint))
|
||||
{
|
||||
isZooming = true;
|
||||
AddSelectionAdorner();
|
||||
CaptureMouse();
|
||||
shouldKeepRatioWhileZooming = Keyboard.Modifiers == ModifierKeys.Shift;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// dragging
|
||||
bool shouldStartDrag = ShouldStartPanning(e);
|
||||
if (shouldStartDrag)
|
||||
StartPanning(e);
|
||||
|
||||
// zooming
|
||||
bool shouldStartZoom = ShouldStartZoom(e);
|
||||
if (shouldStartZoom)
|
||||
StartZoom(e);
|
||||
|
||||
if (!Plotter.IsFocused)
|
||||
{
|
||||
//var window = Window.GetWindow(Plotter);
|
||||
//var focusWithinWindow = FocusManager.GetFocusedElement(window) != null;
|
||||
|
||||
Plotter.Focus();
|
||||
|
||||
//if (!focusWithinWindow)
|
||||
//{
|
||||
|
||||
// this is done to prevent other tools like PointSelector from getting mouse click event when clicking on plotter
|
||||
// to activate window it's contained within
|
||||
e.Handled = true;
|
||||
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isPanning && !isZooming) return;
|
||||
|
||||
// dragging
|
||||
if (isPanning && e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
if (!IsMouseCaptured)
|
||||
{
|
||||
CaptureMouse();
|
||||
}
|
||||
|
||||
Point endPoint = e.GetPosition(this).ScreenToViewport(Viewport.Transform);
|
||||
|
||||
Point loc = Viewport.Visible.Location;
|
||||
Vector shift = panningStartPointInViewport - endPoint;
|
||||
loc += shift;
|
||||
|
||||
// preventing unnecessary changes, if actually visible hasn't change.
|
||||
if (shift.X != 0 || shift.Y != 0)
|
||||
{
|
||||
Cursor = Cursors.ScrollAll;
|
||||
|
||||
DataRect visible = Viewport.Visible;
|
||||
|
||||
visible.Location = loc;
|
||||
Viewport.Visible = visible;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
// zooming
|
||||
else if (isZooming && e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Point zoomEndPoint = e.GetPosition(this);
|
||||
UpdateZoomRect(zoomEndPoint);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsShiftPressed()
|
||||
{
|
||||
return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
}
|
||||
|
||||
private void UpdateZoomRect(Point zoomEndPoint)
|
||||
{
|
||||
Rect output = Viewport.Output;
|
||||
Rect tmpZoomRect = new Rect(zoomStartPoint, zoomEndPoint);
|
||||
tmpZoomRect = Rect.Intersect(tmpZoomRect, output);
|
||||
|
||||
shouldKeepRatioWhileZooming = IsShiftPressed();
|
||||
if (shouldKeepRatioWhileZooming)
|
||||
{
|
||||
double currZoomRatio = tmpZoomRect.Width / tmpZoomRect.Height;
|
||||
double zoomRatio = output.Width / output.Height;
|
||||
if (currZoomRatio < zoomRatio)
|
||||
{
|
||||
double oldHeight = tmpZoomRect.Height;
|
||||
double height = tmpZoomRect.Width / zoomRatio;
|
||||
tmpZoomRect.Height = height;
|
||||
if (!tmpZoomRect.Contains(zoomStartPoint))
|
||||
{
|
||||
tmpZoomRect.Offset(0, oldHeight - height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double oldWidth = tmpZoomRect.Width;
|
||||
double width = tmpZoomRect.Height * zoomRatio;
|
||||
tmpZoomRect.Width = width;
|
||||
if (!tmpZoomRect.Contains(zoomStartPoint))
|
||||
{
|
||||
tmpZoomRect.Offset(oldWidth - width, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoomRect = tmpZoomRect;
|
||||
UpdateSelectionAdorner();
|
||||
}
|
||||
|
||||
private void OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
OnParentMouseUp(e);
|
||||
}
|
||||
|
||||
protected virtual void OnParentMouseUp(MouseButtonEventArgs e)
|
||||
{
|
||||
if (isPanning && e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
isPanning = false;
|
||||
StopPanning(e);
|
||||
}
|
||||
else if (isZooming && e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
isZooming = false;
|
||||
StopZooming();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void StopZooming()
|
||||
{
|
||||
if (zoomRect.HasValue)
|
||||
{
|
||||
Rect output = Viewport.Output;
|
||||
|
||||
Point p1 = zoomRect.Value.TopLeft.ScreenToViewport(Viewport.Transform);
|
||||
Point p2 = zoomRect.Value.BottomRight.ScreenToViewport(Viewport.Transform);
|
||||
DataRect newVisible = new DataRect(p1, p2);
|
||||
Viewport.Visible = newVisible;
|
||||
|
||||
zoomRect = null;
|
||||
ReleaseMouseCapture();
|
||||
RemoveSelectionAdorner();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void StopPanning(MouseButtonEventArgs e)
|
||||
{
|
||||
Plotter2D.UndoProvider.CaptureNewValue(Plotter2D.Viewport, Viewport2D.VisibleProperty, Viewport.Visible);
|
||||
|
||||
if (!Plotter.IsFocused)
|
||||
{
|
||||
Plotter2D.Focus();
|
||||
}
|
||||
|
||||
Plotter2D.Viewport.PanningState = Viewport2DPanningState.NotPanning;
|
||||
|
||||
ReleaseMouseCapture();
|
||||
ClearValue(CursorProperty);
|
||||
}
|
||||
|
||||
protected override void OnLostFocus(RoutedEventArgs e)
|
||||
{
|
||||
if (isZooming)
|
||||
{
|
||||
RemoveSelectionAdorner();
|
||||
isZooming = false;
|
||||
}
|
||||
if (isPanning)
|
||||
{
|
||||
Plotter2D.Viewport.PanningState = Viewport2DPanningState.NotPanning;
|
||||
isPanning = false;
|
||||
}
|
||||
ReleaseMouseCapture();
|
||||
base.OnLostFocus(e);
|
||||
}
|
||||
|
||||
private void MouseWheelZoom(Point mousePos, double wheelRotationDelta)
|
||||
{
|
||||
Point zoomTo = mousePos.ScreenToViewport(Viewport.Transform);
|
||||
|
||||
double zoomSpeed = Math.Abs(wheelRotationDelta / Mouse.MouseWheelDeltaForOneLine);
|
||||
zoomSpeed *= wheelZoomSpeed;
|
||||
if (wheelRotationDelta < 0)
|
||||
{
|
||||
zoomSpeed = 1 / zoomSpeed;
|
||||
}
|
||||
Viewport.Visible = Viewport.Visible.Zoom(zoomTo, zoomSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user