Browse Source

Merge pull request #80 from PixiEditor/moveVPTool

Added Move Viewport (Pan) Tool
Krzysztof Krysiński 4 years ago
parent
commit
83ce96efeb

+ 9 - 4
PixiEditor/Helpers/GlobalMouseHook.cs

@@ -4,6 +4,7 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows;
+using System.Windows.Input;
 
 
 namespace PixiEditor.Helpers
 namespace PixiEditor.Helpers
 {
 {
@@ -32,7 +33,7 @@ namespace PixiEditor.Helpers
 
 
         public static void RaiseMouseUp()
         public static void RaiseMouseUp()
         {
         {
-            MouseUp?.Invoke(default, default);
+            MouseUp?.Invoke(default, default, default);
         }
         }
 
 
         private static void Unsubscribe()
         private static void Unsubscribe()
@@ -73,11 +74,13 @@ namespace PixiEditor.Helpers
             if (nCode >= 0)
             if (nCode >= 0)
             {
             {
                 MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
                 MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
-                if (wParam == WM_LBUTTONUP)
+                if (wParam == WM_LBUTTONUP || wParam == WM_MBUTTONUP || wParam == WM_RBUTTONUP)
                 {
                 {
                     if (MouseUp != null)
                     if (MouseUp != null)
                     {
                     {
-                        MouseUp.Invoke(null, new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y));
+                        MouseButton button = wParam == WM_LBUTTONUP ? MouseButton.Left
+                            : wParam == WM_MBUTTONUP ? MouseButton.Middle : MouseButton.Right;
+                        MouseUp.Invoke(null, new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y), button);
                     }
                     }
                 }
                 }
             }
             }
@@ -86,6 +89,8 @@ namespace PixiEditor.Helpers
 
 
         private const int WH_MOUSE_LL = 14;
         private const int WH_MOUSE_LL = 14;
         private const int WM_LBUTTONUP = 0x0202;
         private const int WM_LBUTTONUP = 0x0202;
+        private const int WM_MBUTTONUP = 0x0208;
+        private const int WM_RBUTTONUP = 0x0205;
 
 
         [StructLayout(LayoutKind.Sequential)]
         [StructLayout(LayoutKind.Sequential)]
         private struct POINT
         private struct POINT
@@ -127,5 +132,5 @@ namespace PixiEditor.Helpers
         private static extern IntPtr GetModuleHandle(string name);
         private static extern IntPtr GetModuleHandle(string name);
     }
     }
 
 
-    public delegate void MouseUpEventHandler(object sender, Point p);
+    public delegate void MouseUpEventHandler(object sender, Point p, MouseButton button);
 }
 }

+ 16 - 3
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -12,6 +12,7 @@ using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
+using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 
 
 namespace PixiEditor.Models.Controllers
 namespace PixiEditor.Models.Controllers
@@ -84,6 +85,8 @@ namespace PixiEditor.Models.Controllers
             MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
             MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
             MouseController.MousePositionChanged += Controller_MousePositionChanged;
             MouseController.MousePositionChanged += Controller_MousePositionChanged;
             MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
             MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
+            MouseController.OnMouseDown += MouseController_OnMouseDown;
+            MouseController.OnMouseUp += MouseController_OnMouseUp;
             BitmapOperations = new BitmapOperationsUtility(this);
             BitmapOperations = new BitmapOperationsUtility(this);
             ReadonlyToolUtility = new ReadonlyToolUtility();
             ReadonlyToolUtility = new ReadonlyToolUtility();
         }
         }
@@ -91,6 +94,16 @@ namespace PixiEditor.Models.Controllers
         public event EventHandler<LayersChangedEventArgs> LayersChanged;
         public event EventHandler<LayersChangedEventArgs> LayersChanged;
         public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
         public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
 
 
+        private void MouseController_OnMouseDown(object sender, MouseEventArgs e)
+        {
+            SelectedTool.OnMouseDown(e);
+        }
+
+        private void MouseController_OnMouseUp(object sender, MouseEventArgs e)
+        {
+            SelectedTool.OnMouseUp(e);
+        }
+
         public void SetActiveTool(Tool tool)
         public void SetActiveTool(Tool tool)
         {
         {
             PreviewLayer = null;
             PreviewLayer = null;
@@ -174,18 +187,18 @@ namespace PixiEditor.Models.Controllers
 
 
         private bool IsDraggingViewport()
         private bool IsDraggingViewport()
         {
         {
-            return Keyboard.IsKeyDown(Key.LeftShift) && !(SelectedTool is ShapeTool);
+            return SelectedTool is MoveViewportTool;
         }
         }
 
 
         private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
         private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
         {
         {
-            SelectedTool.OnMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            SelectedTool.OnRecordingLeftMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
             PreviewLayer = null;
             PreviewLayer = null;
         }
         }
 
 
         private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
         private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
         {
         {
-            SelectedTool.OnMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            SelectedTool.OnStoppedRecordingMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
             if (IsOperationTool(SelectedTool) && ((BitmapOperationTool) SelectedTool).RequiresPreviewLayer)
             if (IsOperationTool(SelectedTool) && ((BitmapOperationTool) SelectedTool).RequiresPreviewLayer)
                 BitmapOperations.StopAction();
                 BitmapOperations.StopAction();
         }
         }

+ 19 - 0
PixiEditor/Models/Controllers/MouseMovementController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Windows.Input;
 using Accessibility;
 using Accessibility;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 
 
@@ -11,6 +12,8 @@ namespace PixiEditor.Models.Controllers
         public bool IsRecordingChanges { get; private set; }
         public bool IsRecordingChanges { get; private set; }
         public bool ClickedOnCanvas { get; set; }
         public bool ClickedOnCanvas { get; set; }
         public event EventHandler StartedRecordingChanges;
         public event EventHandler StartedRecordingChanges;
+        public event EventHandler<MouseEventArgs> OnMouseDown;
+        public event EventHandler<MouseEventArgs> OnMouseUp;
         public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
         public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
         public event EventHandler StoppedRecordingChanges;
         public event EventHandler StoppedRecordingChanges;
 
 
@@ -44,6 +47,22 @@ namespace PixiEditor.Models.Controllers
             MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
             MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
         }
         }
 
 
+        /// <summary>
+        /// Plain mouse down, does not affect mouse recordings
+        /// </summary>
+        public void MouseDown(MouseEventArgs args)
+        {
+            OnMouseDown?.Invoke(this, args);
+        }
+
+        /// <summary>
+        /// Plain mouse up, does not affect mouse recordings
+        /// </summary>
+        public void MouseUp(MouseEventArgs args)
+        {
+            OnMouseUp?.Invoke(this, args);
+        }
+
         public void StopRecordingMouseMovementChanges()
         public void StopRecordingMouseMovementChanges()
         {
         {
             if (IsRecordingChanges)
             if (IsRecordingChanges)

+ 4 - 1
PixiEditor/Models/Tools/Tool.cs

@@ -30,9 +30,12 @@ namespace PixiEditor.Models.Tools
         public bool CanStartOutsideCanvas { get; set; } = false;
         public bool CanStartOutsideCanvas { get; set; } = false;
 
 
         public virtual void OnMouseDown(MouseEventArgs e) { }
         public virtual void OnMouseDown(MouseEventArgs e) { }
-
         public virtual void OnMouseUp(MouseEventArgs e) { }
         public virtual void OnMouseUp(MouseEventArgs e) { }
 
 
+        public virtual void OnRecordingLeftMouseDown(MouseEventArgs e) { }
+
+        public virtual void OnStoppedRecordingMouseUp(MouseEventArgs e) { }
+
         public virtual void OnMouseMove(MouseEventArgs e) { }
         public virtual void OnMouseMove(MouseEventArgs e) { }
 
 
         public virtual void AfterAddedUndo() { }
         public virtual void AfterAddedUndo() { }

+ 1 - 0
PixiEditor/Models/Tools/ToolType.cs

@@ -3,6 +3,7 @@
     public enum ToolType
     public enum ToolType
     {
     {
         None,
         None,
+        MoveViewport,
         Move,
         Move,
         Pen,
         Pen,
         Select,
         Select,

+ 1 - 1
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -28,7 +28,7 @@ namespace PixiEditor.Models.Tools.Tools
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
         }
         }
 
 
-        public override void OnMouseDown(MouseEventArgs e)
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
         {
             _pixelsVisited.Clear();
             _pixelsVisited.Clear();
         }
         }

+ 1 - 1
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -62,7 +62,7 @@ namespace PixiEditor.Models.Tools.Tools
             }
             }
         }
         }
 
 
-        public override void OnMouseUp(MouseEventArgs e) //This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e) //This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
         {   //is because it doesn't fire if no pixel changes were made.
         {   //is because it doesn't fire if no pixel changes were made.
             if (_currentSelection != null && _currentSelection.Length == 0)
             if (_currentSelection != null && _currentSelection.Length == 0)
             {
             {

+ 50 - 0
PixiEditor/Models/Tools/Tools/MoveViewportTool.cs

@@ -0,0 +1,50 @@
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
+using System.Drawing;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class MoveViewportTool : ReadonlyTool
+    {
+        public override ToolType ToolType => ToolType.MoveViewport;
+        private Point _clickPoint;
+
+        public MoveViewportTool()
+        {
+            HideHighlight = true;
+            Cursor = Cursors.SizeAll;
+            Tooltip = "Move viewport. (H)";
+        }
+
+        public override void OnMouseDown(MouseEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
+            {
+                _clickPoint = MousePositionConverter.GetCursorPosition();
+            }
+        }
+
+        public override void OnMouseMove(MouseEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
+            {
+                var point = MousePositionConverter.GetCursorPosition();
+                ViewModelMain.Current.ViewportPosition = new System.Windows.Point(point.X - _clickPoint.X, 
+                    point.Y - _clickPoint.Y);
+            }
+        }
+
+        public override void OnMouseUp(MouseEventArgs e)
+        {
+            if (e.MiddleButton == MouseButtonState.Pressed)
+            {
+                ViewModelMain.Current.SetActiveTool(ViewModelMain.Current.LastActionTool);
+            }
+        }
+
+        public override void Use(Coordinates[] pixels)
+        {
+        }
+    }
+}

+ 2 - 2
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -25,7 +25,7 @@ namespace PixiEditor.Models.Tools.Tools
             Toolbar = new SelectToolToolbar();
             Toolbar = new SelectToolToolbar();
         }
         }
 
 
-        public override void OnMouseDown(MouseEventArgs e)
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
         {
             Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out SelectionType);
             Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out SelectionType);
 
 
@@ -35,7 +35,7 @@ namespace PixiEditor.Models.Tools.Tools
                 _oldSelection = ViewModelMain.Current.ActiveSelection;
                 _oldSelection = ViewModelMain.Current.ActiveSelection;
         }
         }
 
 
-        public override void OnMouseUp(MouseEventArgs e)
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
         {
         {
             if (ViewModelMain.Current.ActiveSelection.SelectedPoints.Count() <= 1)
             if (ViewModelMain.Current.ActiveSelection.SelectedPoints.Count() <= 1)
             {
             {

+ 2 - 2
PixiEditor/Models/Tools/Tools/ZoomTool.cs

@@ -25,7 +25,7 @@ namespace PixiEditor.Models.Tools.Tools
             _pixelsPerZoomMultiplier = _workAreaWidth / ZoomSensitivityMultiplier;
             _pixelsPerZoomMultiplier = _workAreaWidth / ZoomSensitivityMultiplier;
         }
         }
 
 
-        public override void OnMouseDown(MouseEventArgs e)
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
         {
             _startingX = MousePositionConverter.GetCursorPosition().X;
             _startingX = MousePositionConverter.GetCursorPosition().X;
             ViewModelMain.Current.ZoomPercentage = 100; //This resest the value, so callback in MainDrawingPanel can fire again later
             ViewModelMain.Current.ZoomPercentage = 100; //This resest the value, so callback in MainDrawingPanel can fire again later
@@ -43,7 +43,7 @@ namespace PixiEditor.Models.Tools.Tools
             }
             }
         }
         }
 
 
-        public override void OnMouseUp(MouseEventArgs e)
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
         {
         {
             if (e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released && 
             if (e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released && 
                 _startingX == MousePositionConverter.GetCursorPosition().X)
                 _startingX == MousePositionConverter.GetCursorPosition().X)

+ 27 - 6
PixiEditor/ViewModels/ViewModelMain.cs

@@ -196,6 +196,19 @@ namespace PixiEditor.ViewModels
             }
             }
         }
         }
 
 
+        private Point _viewPortPosition;
+
+        public Point ViewportPosition
+        {
+            get => _viewPortPosition;
+            set 
+            {
+                _viewPortPosition = value;
+                RaisePropertyChanged(nameof(ViewportPosition));
+            }
+        }
+
+
         private bool _updateReadyToInstall = false;
         private bool _updateReadyToInstall = false;
 
 
         public bool UpdateReadyToInstall
         public bool UpdateReadyToInstall
@@ -224,7 +237,7 @@ namespace PixiEditor.ViewModels
         }
         }
 
 
         private bool _restoreToolOnKeyUp = false;
         private bool _restoreToolOnKeyUp = false;
-        private Tool _lastActionTool;
+        public Tool LastActionTool { get; private set; }
 
 
         public UpdateChecker UpdateChecker { get; set; }
         public UpdateChecker UpdateChecker { get; set; }
 
 
@@ -273,7 +286,7 @@ namespace PixiEditor.ViewModels
             RestartApplicationCommand = new RelayCommand(RestartApplication);
             RestartApplicationCommand = new RelayCommand(RestartApplication);
             ToolSet = new ObservableCollection<Tool>
             ToolSet = new ObservableCollection<Tool>
             {
             {
-                new MoveTool(), new PenTool(), new SelectTool(), new FloodFill(), new LineTool(),
+                new MoveViewportTool(), new MoveTool(), new PenTool(), new SelectTool(), new FloodFill(), new LineTool(),
                 new CircleTool(), new RectangleTool(), new EraserTool(), new ColorPickerTool(), new BrightnessTool(), 
                 new CircleTool(), new RectangleTool(), new EraserTool(), new ColorPickerTool(), new BrightnessTool(), 
                 new ZoomTool()
                 new ZoomTool()
             };
             };
@@ -293,6 +306,7 @@ namespace PixiEditor.ViewModels
                     new Shortcut(Key.V, SelectToolCommand, ToolType.Move),
                     new Shortcut(Key.V, SelectToolCommand, ToolType.Move),
                     new Shortcut(Key.M, SelectToolCommand, ToolType.Select),
                     new Shortcut(Key.M, SelectToolCommand, ToolType.Select),
                     new Shortcut(Key.Z, SelectToolCommand, ToolType.Zoom),
                     new Shortcut(Key.Z, SelectToolCommand, ToolType.Zoom),
+                    new Shortcut(Key.H, SelectToolCommand, ToolType.MoveViewport),
                     new Shortcut(Key.OemPlus, ZoomCommand, 115),
                     new Shortcut(Key.OemPlus, ZoomCommand, 115),
                     new Shortcut(Key.OemMinus, ZoomCommand, 85),
                     new Shortcut(Key.OemMinus, ZoomCommand, 85),
                     new Shortcut(Key.OemOpenBrackets, ChangeToolSizeCommand, -1),
                     new Shortcut(Key.OemOpenBrackets, ChangeToolSizeCommand, -1),
@@ -620,7 +634,7 @@ namespace PixiEditor.ViewModels
             if (_restoreToolOnKeyUp && ShortcutController.LastShortcut != null && ShortcutController.LastShortcut.ShortcutKey == args.Key)
             if (_restoreToolOnKeyUp && ShortcutController.LastShortcut != null && ShortcutController.LastShortcut.ShortcutKey == args.Key)
             {
             {
                 _restoreToolOnKeyUp = false;
                 _restoreToolOnKeyUp = false;
-                SetActiveTool(_lastActionTool);
+                SetActiveTool(LastActionTool);
                 ShortcutController.BlockShortcutExecution = false;
                 ShortcutController.BlockShortcutExecution = false;
             }
             }
         }
         }
@@ -724,7 +738,7 @@ namespace PixiEditor.ViewModels
             if (activeTool != null) activeTool.IsActive = false;
             if (activeTool != null) activeTool.IsActive = false;
 
 
             tool.IsActive = true;
             tool.IsActive = true;
-            _lastActionTool = BitmapManager.SelectedTool;
+            LastActionTool = BitmapManager.SelectedTool;
             BitmapManager.SetActiveTool(tool);
             BitmapManager.SetActiveTool(tool);
             SetToolCursor(tool.ToolType);
             SetToolCursor(tool.ToolType);
         }
         }
@@ -750,6 +764,8 @@ namespace PixiEditor.ViewModels
                     BitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
                     BitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
                 }
                 }
             }
             }
+            BitmapManager.MouseController.MouseDown(new MouseEventArgs(Mouse.PrimaryDevice,
+                (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
 
 
             // Mouse down is guaranteed to only be raised from within this application, so by subscribing here we
             // Mouse down is guaranteed to only be raised from within this application, so by subscribing here we
             // only listen for mouse up events that occurred as a result of a mouse down within this application.
             // only listen for mouse up events that occurred as a result of a mouse down within this application.
@@ -758,10 +774,15 @@ namespace PixiEditor.ViewModels
         }
         }
 
 
         // this is public for testing.
         // this is public for testing.
-        public void MouseHook_OnMouseUp(object sender, Point p)
+        public void MouseHook_OnMouseUp(object sender, Point p, MouseButton button)
         {
         {
             GlobalMouseHook.OnMouseUp -= MouseHook_OnMouseUp;
             GlobalMouseHook.OnMouseUp -= MouseHook_OnMouseUp;
-            BitmapManager.MouseController.StopRecordingMouseMovementChanges();
+            if (button == MouseButton.Left)
+            {
+                BitmapManager.MouseController.StopRecordingMouseMovementChanges();
+            }
+            BitmapManager.MouseController.MouseUp(new MouseEventArgs(Mouse.PrimaryDevice, 
+                (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 1
PixiEditor/Views/MainDrawingPanel.xaml

@@ -10,7 +10,8 @@
              mc:Ignorable="d" PreviewMouseDown="mainDrawingPanel_MouseDown" PreviewMouseUp="mainDrawingPanel_PreviewMouseUp"
              mc:Ignorable="d" PreviewMouseDown="mainDrawingPanel_MouseDown" PreviewMouseUp="mainDrawingPanel_PreviewMouseUp"
              d:DesignHeight="450" d:DesignWidth="800" x:Name="mainDrawingPanel" PreviewMouseWheel="Zoombox_MouseWheel">
              d:DesignHeight="450" d:DesignWidth="800" x:Name="mainDrawingPanel" PreviewMouseWheel="Zoombox_MouseWheel">
     <xctk:Zoombox PreviewMouseDown="Zoombox_PreviewMouseDown" Cursor="{Binding Cursor}" Name="Zoombox" KeepContentInBounds="True"
     <xctk:Zoombox PreviewMouseDown="Zoombox_PreviewMouseDown" Cursor="{Binding Cursor}" Name="Zoombox" KeepContentInBounds="True"
-                  Loaded="Zoombox_Loaded" IsAnimated="False" CurrentViewChanged="Zoombox_CurrentViewChanged" DragModifiers="Shift" ZoomModifiers="None">
+                  Loaded="Zoombox_Loaded" IsAnimated="False" MouseDown="Zoombox_MouseDown"
+                  CurrentViewChanged="Zoombox_CurrentViewChanged" DragModifiers="Blocked" ZoomModifiers="None">
         <i:Interaction.Triggers>
         <i:Interaction.Triggers>
             <i:EventTrigger EventName="MouseMove">
             <i:EventTrigger EventName="MouseMove">
                 <i:InvokeCommandAction Command="{Binding MouseMoveCommand, ElementName=mainDrawingPanel, Mode=OneWay}" />
                 <i:InvokeCommandAction Command="{Binding MouseMoveCommand, ElementName=mainDrawingPanel, Mode=OneWay}" />

+ 67 - 0
PixiEditor/Views/MainDrawingPanel.xaml.cs

@@ -7,6 +7,7 @@ using System.Windows.Input;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.Tools;
 using Xceed.Wpf.Toolkit.Core.Input;
 using Xceed.Wpf.Toolkit.Core.Input;
 using Xceed.Wpf.Toolkit.Zoombox;
 using Xceed.Wpf.Toolkit.Zoombox;
+using PixiEditor.Models.Position;
 
 
 namespace PixiEditor.Views
 namespace PixiEditor.Views
 {
 {
@@ -58,6 +59,18 @@ namespace PixiEditor.Views
             DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(0.0, ZoomPercentegeChanged));
             DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(0.0, ZoomPercentegeChanged));
 
 
 
 
+
+        public Point ViewportPosition
+        {
+            get { return (Point)GetValue(ViewportPositionProperty); }
+            set { SetValue(ViewportPositionProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for ViewportPosition.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ViewportPositionProperty =
+            DependencyProperty.Register("ViewportPosition", typeof(Point),
+                typeof(MainDrawingPanel), new PropertyMetadata(default(Point), ViewportPosCallback));
+
         public bool Center
         public bool Center
         {
         {
             get => (bool) GetValue(CenterProperty);
             get => (bool) GetValue(CenterProperty);
@@ -103,6 +116,31 @@ namespace PixiEditor.Views
             set => SetValue(IsUsingZoomToolProperty, value);
             set => SetValue(IsUsingZoomToolProperty, value);
         }
         }
 
 
+        public ICommand MiddleMouseClickedCommand
+        {
+            get { return (ICommand)GetValue(MiddleMouseClickedCommandProperty); }
+            set { SetValue(MiddleMouseClickedCommandProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MiddleMouseClickedCommand.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MiddleMouseClickedCommandProperty =
+            DependencyProperty.Register("MiddleMouseClickedCommand", typeof(ICommand), typeof(MainDrawingPanel), new PropertyMetadata(default(ICommand)));
+
+
+
+        public object MiddleMouseClickedCommandParameter
+        {
+            get { return (object)GetValue(MiddleMouseClickedCommandParameterProperty); }
+            set { SetValue(MiddleMouseClickedCommandParameterProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MiddleMouseClickedCommandParameter.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MiddleMouseClickedCommandParameterProperty =
+            DependencyProperty.Register("MiddleMouseClickedCommandParameter", typeof(object), typeof(MainDrawingPanel), 
+                new PropertyMetadata(default(object)));
+
+
+
         private static void ZoomPercentegeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         private static void ZoomPercentegeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
         {
             MainDrawingPanel panel = (MainDrawingPanel)d;
             MainDrawingPanel panel = (MainDrawingPanel)d;
@@ -115,6 +153,7 @@ namespace PixiEditor.Views
         }
         }
 
 
         public double ClickScale;
         public double ClickScale;
+        public Point ClickPosition;
 
 
         public MainDrawingPanel()
         public MainDrawingPanel()
         {
         {
@@ -122,6 +161,24 @@ namespace PixiEditor.Views
             Zoombox.ZoomToSelectionModifiers = new KeyModifierCollection() { KeyModifier.RightAlt };
             Zoombox.ZoomToSelectionModifiers = new KeyModifierCollection() { KeyModifier.RightAlt };
         }
         }
 
 
+        private static void ViewportPosCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            MainDrawingPanel panel = (MainDrawingPanel)d;
+            if (PresentationSource.FromVisual(panel.Zoombox) == null)
+            {
+                panel.Zoombox.Position = default;
+                return;
+            }           
+            TranslateZoombox(panel, (Point)e.NewValue);
+        }
+
+        private static void TranslateZoombox(MainDrawingPanel panel, Point vector)
+        {
+            var newPos = new Point(panel.ClickPosition.X + vector.X,
+                panel.ClickPosition.Y + vector.Y);
+            panel.Zoombox.Position = newPos;
+        }
+
         private void Zoombox_CurrentViewChanged(object sender, ZoomboxViewChangedEventArgs e)
         private void Zoombox_CurrentViewChanged(object sender, ZoomboxViewChangedEventArgs e)
         {
         {
             Zoombox.MinScale = 32 / ((FrameworkElement)Item).Width;
             Zoombox.MinScale = 32 / ((FrameworkElement)Item).Width;
@@ -173,6 +230,7 @@ namespace PixiEditor.Views
         {
         {
             IsUsingZoomTool = ViewModelMain.Current.BitmapManager.SelectedTool is ZoomTool;
             IsUsingZoomTool = ViewModelMain.Current.BitmapManager.SelectedTool is ZoomTool;
             Mouse.Capture((IInputElement)sender, CaptureMode.SubTree);
             Mouse.Capture((IInputElement)sender, CaptureMode.SubTree);
+            ClickPosition = ((FrameworkElement)Item).TranslatePoint(new Point(0, 0), Zoombox);
             SetClickValues();
             SetClickValues();
         }
         }
 
 
@@ -180,5 +238,14 @@ namespace PixiEditor.Views
         {
         {
             ((IInputElement)sender).ReleaseMouseCapture();
             ((IInputElement)sender).ReleaseMouseCapture();
         }
         }
+
+        private void Zoombox_MouseDown(object sender, MouseButtonEventArgs e)
+        {
+            if (e.MiddleButton == MouseButtonState.Pressed && 
+                MiddleMouseClickedCommand.CanExecute(MiddleMouseClickedCommandParameter))
+            {
+                MiddleMouseClickedCommand.Execute(MiddleMouseClickedCommandParameter);
+            }
+        }
     }
     }
 }
 }

+ 5 - 2
PixiEditor/Views/MainWindow.xaml

@@ -5,7 +5,7 @@
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:vm="clr-namespace:PixiEditor.ViewModels"
         xmlns:vm="clr-namespace:PixiEditor.ViewModels"
         xmlns:vws="clr-namespace:PixiEditor.Views"
         xmlns:vws="clr-namespace:PixiEditor.Views"
-        xmlns:helpers="clr-namespace:PixiEditor.Helpers"
+        xmlns:tools="clr-namespace:PixiEditor.Models.Tools"
         xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
         xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
         xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
         xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
@@ -163,7 +163,10 @@
         <Grid Grid.Column="1" Grid.Row="2" Background="#303030" Margin="0,7,5,0">
         <Grid Grid.Column="1" Grid.Row="2" Background="#303030" Margin="0,7,5,0">
             <Grid>
             <Grid>
                 <vws:MainDrawingPanel ZoomPercentage="{Binding ZoomPercentage, Mode=TwoWay}" Center="{Binding RecenterZoombox, Mode=TwoWay}" x:Name="DrawingPanel"
                 <vws:MainDrawingPanel ZoomPercentage="{Binding ZoomPercentage, Mode=TwoWay}" Center="{Binding RecenterZoombox, Mode=TwoWay}" x:Name="DrawingPanel"
-                                      CenterOnStart="True" Cursor="{Binding ToolCursor}">
+                                      CenterOnStart="True" Cursor="{Binding ToolCursor}" 
+                                      MiddleMouseClickedCommand="{Binding SelectToolCommand}" 
+                                      MiddleMouseClickedCommandParameter="{x:Static tools:ToolType.MoveViewport}"
+                                      ViewportPosition="{Binding ViewportPosition, Mode=TwoWay}">
                     <i:Interaction.Triggers>
                     <i:Interaction.Triggers>
                         <i:EventTrigger EventName="MouseMove">
                         <i:EventTrigger EventName="MouseMove">
                             <i:InvokeCommandAction Command="{Binding MouseMoveCommand}" />
                             <i:InvokeCommandAction Command="{Binding MouseMoveCommand}" />

+ 1 - 1
PixiEditor/Views/MainWindow.xaml.cs

@@ -22,7 +22,7 @@ namespace PixiEditor
             InitializeComponent();
             InitializeComponent();
             StateChanged += MainWindowStateChangeRaised;
             StateChanged += MainWindowStateChangeRaised;
             MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
             MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
-            viewModel = ((ViewModelMain)DataContext);
+            viewModel = (ViewModelMain)DataContext;
             viewModel.CloseAction = Close;
             viewModel.CloseAction = Close;
         }
         }
 
 

+ 1 - 1
PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

@@ -88,7 +88,7 @@ namespace PixiEditorTests.ViewModelsTests
 
 
             Assert.True(viewModel.BitmapManager.MouseController.IsRecordingChanges);
             Assert.True(viewModel.BitmapManager.MouseController.IsRecordingChanges);
 
 
-            viewModel.MouseHook_OnMouseUp(default, default);
+            viewModel.MouseHook_OnMouseUp(default, default, default);
 
 
             Assert.False(viewModel.BitmapManager.MouseController.IsRecordingChanges);
             Assert.False(viewModel.BitmapManager.MouseController.IsRecordingChanges);
         }
         }