Browse Source

Redesign the input system and tool execution

Equbuxu 3 years ago
parent
commit
b03ff7e75e
39 changed files with 951 additions and 1025 deletions
  1. 85 0
      PixiEditor/Helpers/CoordinatesHelper.cs
  2. 1 0
      PixiEditor/Helpers/GlobalMouseHook.cs
  3. 1 1
      PixiEditor/Helpers/RelayCommand.cs
  4. 127 159
      PixiEditor/Models/Controllers/BitmapManager.cs
  5. 32 168
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  6. 15 0
      PixiEditor/Models/Controllers/ICanvasInputTarget.cs
  7. 53 0
      PixiEditor/Models/Controllers/MouseInputFilter.cs
  8. 0 94
      PixiEditor/Models/Controllers/MouseMovementController.cs
  9. 0 14
      PixiEditor/Models/Controllers/ReadonlyToolUtility.cs
  10. 97 0
      PixiEditor/Models/Controllers/ToolSession.cs
  11. 140 0
      PixiEditor/Models/Controllers/ToolSessionController.cs
  12. 0 2
      PixiEditor/Models/DataHolders/Document/Document.cs
  13. 16 20
      PixiEditor/Models/Tools/BitmapOperationTool.cs
  14. 4 4
      PixiEditor/Models/Tools/ReadonlyTool.cs
  15. 0 5
      PixiEditor/Models/Tools/ShapeTool.cs
  16. 16 55
      PixiEditor/Models/Tools/Tool.cs
  17. 17 31
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  18. 30 37
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  19. 10 32
      PixiEditor/Models/Tools/Tools/ColorPickerTool.cs
  20. 6 9
      PixiEditor/Models/Tools/Tools/EraserTool.cs
  21. 7 8
      PixiEditor/Models/Tools/Tools/FloodFillTool.cs
  22. 91 97
      PixiEditor/Models/Tools/Tools/LineTool.cs
  23. 13 22
      PixiEditor/Models/Tools/Tools/MagicWandTool.cs
  24. 53 67
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  25. 4 5
      PixiEditor/Models/Tools/Tools/MoveViewportTool.cs
  26. 12 13
      PixiEditor/Models/Tools/Tools/PenTool.cs
  27. 11 22
      PixiEditor/Models/Tools/Tools/RectangleTool.cs
  28. 12 16
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  29. 6 7
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  30. 45 79
      PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  31. 4 6
      PixiEditor/ViewModels/SubViewModels/Main/StylusViewModel.cs
  32. 11 15
      PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  33. 3 16
      PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs
  34. 4 9
      PixiEditor/ViewModels/ViewModelMain.cs
  35. 1 0
      PixiEditor/Views/MainWindow.xaml
  36. 5 2
      PixiEditor/Views/UserControls/DrawingViewPort.xaml
  37. 10 1
      PixiEditor/Views/UserControls/DrawingViewPort.xaml.cs
  38. 2 2
      PixiEditor/Views/UserControls/Layers/LayerGroupControl.xaml.cs
  39. 7 7
      PixiEditorTests/ModelsTests/ControllersTests/MouseMovementControllerTests.cs

+ 85 - 0
PixiEditor/Helpers/CoordinatesHelper.cs

@@ -0,0 +1,85 @@
+using PixiEditor.Models.Position;
+using System;
+using System.Collections.Generic;
+
+namespace PixiEditor.Helpers
+{
+    internal class CoordinatesHelper
+    {
+        public static (Coordinates, Coordinates) GetSquareOrLineCoordinates(IReadOnlyList<Coordinates> coords)
+        {
+            if (DoCoordsFormLine(coords))
+            {
+                return GetLineCoordinates(coords);
+            }
+            return GetSquareCoordiantes(coords);
+        }
+
+        private static bool DoCoordsFormLine(IReadOnlyList<Coordinates> coords)
+        {
+            var p1 = coords[0];
+            var p2 = coords[^1];
+            //find delta and mirror to first quadrant
+            float dX = Math.Abs(p2.X - p1.X);
+            float dY = Math.Abs(p2.Y - p1.Y);
+
+            //normalize
+            float length = (float)Math.Sqrt(dX * dX + dY * dY);
+            if (length == 0)
+                return false;
+            dX = dX / length;
+            dY = dY / length;
+
+            return dX < 0.25f || dY < 0.25f; //angle < 15 deg or angle > 75 deg (sin 15 ~= 0.25)
+        }
+
+        public static (Coordinates, Coordinates) GetLineCoordinates(IReadOnlyList<Coordinates> mouseMoveCords)
+        {
+            int xStart = mouseMoveCords[0].X;
+            int yStart = mouseMoveCords[0].Y;
+
+            int xEnd = mouseMoveCords[^1].X;
+            int yEnd = mouseMoveCords[^1].Y;
+
+
+            if (Math.Abs(xStart - xEnd) > Math.Abs(yStart - yEnd))
+            {
+                yEnd = yStart;
+            }
+            else
+            {
+                xEnd = xStart;
+            }
+            return (new(xStart, yStart), new(xEnd, yEnd));
+        }
+
+        /// <summary>
+        ///     Extracts square from rectangle mouse drag, used to draw symmetric shapes.
+        /// </summary>
+        public static (Coordinates, Coordinates) GetSquareCoordiantes(IReadOnlyList<Coordinates> mouseMoveCords)
+        {
+            var end = mouseMoveCords[^1];
+            var start = mouseMoveCords[0];
+
+            //find delta and mirror to first quadrant
+            var dX = Math.Abs(start.X - end.X);
+            var dY = Math.Abs(start.Y - end.Y);
+
+            float sqrt2 = (float)Math.Sqrt(2);
+            //vector of length 1 at 45 degrees;
+            float diagX, diagY;
+            diagX = diagY = 1 / sqrt2;
+
+            //dot product of delta and diag, returns length of [delta projected onto diag]
+            float projectedLength = diagX * dX + diagY * dY;
+            //project above onto axes
+            float axisLength = projectedLength / sqrt2;
+
+            //final coords
+            float x = -Math.Sign(start.X - end.X) * axisLength;
+            float y = -Math.Sign(start.Y - end.Y) * axisLength;
+            end = new Coordinates((int)x + start.X, (int)y + start.Y);
+            return (start, end);
+        }
+    }
+}

+ 1 - 0
PixiEditor/Helpers/GlobalMouseHook.cs

@@ -88,6 +88,7 @@ namespace PixiEditor.Helpers
                 {
                     if (MouseUp != null)
                     {
+
                         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);

+ 1 - 1
PixiEditor/Helpers/RelayCommand.cs

@@ -40,4 +40,4 @@ namespace PixiEditor.Helpers
             execute(parameter);
         }
     }
-}
+}

+ 127 - 159
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -3,98 +3,92 @@ using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Events;
 using PixiEditor.Models.Layers;
 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.ViewModels;
 using PixiEditor.ViewModels.SubViewModels.Main;
 using SkiaSharp;
 using System;
-using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Diagnostics;
-using System.Linq;
 using System.Windows;
-using System.Windows.Input;
-
+
 namespace PixiEditor.Models.Controllers
 {
     [DebuggerDisplay("{Documents.Count} Document(s)")]
     public class BitmapManager : NotifyableObject
     {
-        private readonly ToolsViewModel _tools;
-
-        private int previewLayerSize;
-        private Document activeDocument;
-        private Coordinates? startPosition = null;
-        private int halfSize;
-        private SKColor _highlightColor;
-        private PenTool _highlightPen;
-        private bool hideReferenceLayer;
-        private bool onlyReferenceLayer;
-
-        public BitmapManager(ToolsViewModel tools)
-        {
-            _tools = tools;
-
-            MouseController = new MouseMovementController();
-            MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
-            MouseController.MousePositionChanged += Controller_MousePositionChanged;
-            MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
-            MouseController.OnMouseDown += MouseController_OnMouseDown;
-            MouseController.OnMouseUp += MouseController_OnMouseUp;
-            MouseController.OnMouseDownCoordinates += MouseController_OnMouseDownCoordinates;
-            BitmapOperations = new BitmapOperationsUtility(this, tools);
-            ReadonlyToolUtility = new ReadonlyToolUtility();
-            DocumentChanged += BitmapManager_DocumentChanged;
-            _highlightPen = new PenTool(this)
-            {
-                AutomaticallyResizeCanvas = false
-            };
-            _highlightColor = new SKColor(0, 0, 0, 77);
-        }
+        private ToolSessionController ToolSessionController { get; set; }
+        public ICanvasInputTarget InputTarget => ToolSessionController;
+        public BitmapOperationsUtility BitmapOperations { get; set; }
 
-        public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
-
-        public MouseMovementController MouseController { get; set; }
-
-        public Layer ActiveLayer => ActiveDocument.ActiveLayer;
-
-        public SKColor PrimaryColor { get; set; }
+        public ObservableCollection<Document> Documents { get; set; } = new ObservableCollection<Document>();
 
-        public BitmapOperationsUtility BitmapOperations { get; set; }
-
-        public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
-
-#nullable enable
+        private Document activeDocument;
         public Document ActiveDocument
         {
             get => activeDocument;
             set
             {
                 activeDocument?.UpdatePreviewImage();
-                Document? oldDoc = activeDocument;
+                Document oldDoc = activeDocument;
                 activeDocument = value;
                 RaisePropertyChanged(nameof(ActiveDocument));
                 DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value, oldDoc));
             }
         }
 
-#nullable disable
-        public ObservableCollection<Document> Documents { get; set; } = new ObservableCollection<Document>();
+        public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
+        public event EventHandler StopUsingTool;
+
+        public Layer ActiveLayer => ActiveDocument.ActiveLayer;
+
+        public SKColor PrimaryColor { get; set; }
 
+        private bool hideReferenceLayer;
         public bool HideReferenceLayer
         {
             get => hideReferenceLayer;
             set => SetProperty(ref hideReferenceLayer, value);
         }
 
+        private bool onlyReferenceLayer;
         public bool OnlyReferenceLayer
         {
             get => onlyReferenceLayer;
             set => SetProperty(ref onlyReferenceLayer, value);
         }
 
+        private readonly ToolsViewModel _tools;
+
+        private int previewLayerSize;
+        private int halfSize;
+        private SKColor _highlightColor;
+        private PenTool _highlightPen;
+
+        private ToolSession activeSession = null;
+
+
+        public BitmapManager(ToolsViewModel tools)
+        {
+            _tools = tools;
+
+            ToolSessionController = new ToolSessionController();
+            ToolSessionController.SessionStarted += OnSessionStart;
+            ToolSessionController.SessionEnded += OnSessionEnd;
+            ToolSessionController.PixelMousePositionChanged += OnPixelMousePositionChange;
+            ToolSessionController.PreciseMousePositionChanged += OnPreciseMousePositionChange;
+            ToolSessionController.KeyStateChanged += (_, _) => UpdateActionDisplay();
+            BitmapOperations = new BitmapOperationsUtility(this, tools);
+
+            DocumentChanged += BitmapManager_DocumentChanged;
+
+            _highlightPen = new PenTool(this)
+            {
+                AutomaticallyResizeCanvas = false
+            };
+            _highlightColor = new SKColor(0, 0, 0, 77);
+        }
+
         public void CloseDocument(Document document)
         {
             int nextIndex = 0;
@@ -107,41 +101,91 @@ namespace PixiEditor.Models.Controllers
             Documents.Remove(document);
             ActiveDocument = nextIndex >= 0 ? Documents[nextIndex] : null;
             document.Dispose();
+        }
+
+        public void UpdateActionDisplay()
+        {
+            _tools.ActiveTool?.UpdateActionDisplay(ToolSessionController.IsCtrlDown, ToolSessionController.IsShiftDown, ToolSessionController.IsAltDown);
+        }
+
+        private void OnSessionStart(object sender, ToolSession e)
+        {
+            activeSession = e;
+
+            ActiveDocument.PreviewLayer.Reset();
+            ExecuteTool();
         }
 
-        public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
-        {
-            Tool activeTool = _tools.ActiveTool;
-
-            if (activeTool.CanStartOutsideCanvas && !clickedOnCanvas)
+        private void OnSessionEnd(object sender, ToolSession e)
+        {
+            activeSession = null;
+
+            if (e.Tool is BitmapOperationTool operationTool && operationTool.RequiresPreviewLayer)
             {
-                return;
+                BitmapOperations.ApplyPreviewLayer();
             }
 
-            if (startPosition == null)
+            ActiveDocument.PreviewLayer.Reset();
+            HighlightPixels(MousePositionConverter.CurrentCoordinates);
+            StopUsingTool?.Invoke(this, EventArgs.Empty);
+        }
+
+        private void OnPreciseMousePositionChange(object sender, (double, double) e)
+        {
+            if (activeSession == null || !activeSession.Tool.RequiresPreciseMouseData)
+                return;
+            ExecuteTool();
+        }
+
+        private void OnPixelMousePositionChange(object sender, MouseMovementEventArgs e)
+        {
+            if (activeSession != null)
             {
-                activeTool.OnStart(newPosition);
-                startPosition = newPosition;
+                if (activeSession.Tool.RequiresPreciseMouseData)
+                    return;
+                ExecuteTool();
+                return;
+            }
+            else
+            {
+                HighlightPixels(e.NewPosition);
             }
+        }
+
+        private void ExecuteTool()
+        {
+            if (activeSession == null)
+                throw new Exception("Can't execute tool's Use outside a session");
 
-            if (activeTool is BitmapOperationTool operationTool)
+            if (activeSession.Tool is BitmapOperationTool operationTool)
             {
-                BitmapOperations.ExecuteTool(newPosition, MouseController.LastMouseMoveCoordinates, operationTool);
+                BitmapOperations.UseTool(activeSession.MouseMovement, operationTool, PrimaryColor);
             }
-            else if (activeTool is ReadonlyTool readonlyTool)
+            else if (activeSession.Tool is ReadonlyTool readonlyTool)
             {
-                ActiveDocument.PreviewLayer.Reset();
-                ReadonlyToolUtility.ExecuteTool(
-                    MouseController.LastMouseMoveCoordinates,
-                    readonlyTool);
+                readonlyTool.Use(activeSession.MouseMovement);
             }
             else
             {
-                throw new InvalidOperationException($"'{activeTool.GetType().Name}' is either not a Tool or can't inherit '{nameof(Tool)}' directly.\nChanges the base type to either '{nameof(BitmapOperationTool)}' or '{nameof(ReadonlyTool)}'");
+                throw new InvalidOperationException($"'{activeSession.Tool.GetType().Name}' is either not a Tool or can't inherit '{nameof(Tool)}' directly.\nChanges the base type to either '{nameof(BitmapOperationTool)}' or '{nameof(ReadonlyTool)}'");
             }
-        }
-
-        public void HighlightPixels(Coordinates newPosition)
+        }
+
+        private void BitmapManager_DocumentChanged(object sender, DocumentChangedEventArgs e)
+        {
+            e.NewDocument?.GeneratePreviewLayer();
+            if (e.OldDocument != e.NewDocument)
+                ToolSessionController.ForceStopActiveSessionIfAny();
+        }
+
+        public void UpdateHighlightIfNecessary(bool forceHide = false)
+        {
+            if (activeSession != null)
+                return;
+            HighlightPixels(forceHide ? new(-1, -1) : ToolSessionController.LastPixelPosition);
+        }
+
+        private void HighlightPixels(Coordinates newPosition)
         {
             if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || _tools.ActiveTool.HideHighlight)
             {
@@ -150,6 +194,15 @@ namespace PixiEditor.Models.Controllers
 
             var previewLayer = ActiveDocument.PreviewLayer;
 
+            if (newPosition.X > ActiveDocument.Width
+                || newPosition.Y > ActiveDocument.Height
+                || newPosition.X < 0 || newPosition.Y < 0)
+            {
+                previewLayer.Reset();
+                previewLayerSize = -1;
+                return;
+            }
+
             if (_tools.ToolSize != previewLayerSize || previewLayer.IsReset)
             {
                 previewLayerSize = _tools.ToolSize;
@@ -160,97 +213,12 @@ namespace PixiEditor.Models.Controllers
 
                 previewLayer.Offset = new Thickness(0, 0, 0, 0);
                 _highlightPen.Draw(previewLayer, cords, cords, _highlightColor, _tools.ToolSize);
-
-                AdjustOffset(newPosition, previewLayer);
-
             }
-
-            previewLayer.InvokeLayerBitmapChange();
-
             AdjustOffset(newPosition, previewLayer);
-
-            if (newPosition.X > ActiveDocument.Width
-                || newPosition.Y > ActiveDocument.Height
-                || newPosition.X < 0 || newPosition.Y < 0)
-            {
-                previewLayer.Reset();
-                previewLayerSize = -1;
-            }
-        }
-
-        private void BitmapManager_DocumentChanged(object sender, DocumentChangedEventArgs e)
-        {
-            e.NewDocument?.GeneratePreviewLayer();
-        }
 
-        private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
-        {
-            Tool activeTool = _tools.ActiveTool;
-
-            if (activeTool == null)
-            {
-                return;
-            }
-
-            activeTool.OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (!MaybeExecuteTool(e.NewPosition) && MouseController.LeftMouseState == MouseButtonState.Released)
-            {
-                HighlightPixels(e.NewPosition);
-            }
-        }
-
-        private void MouseController_OnMouseDown(object sender, MouseEventArgs e)
-        {
-            _tools.ActiveTool.OnMouseDown(e);
-        }
-
-        private void MouseController_OnMouseUp(object sender, MouseEventArgs e)
-        {
-            _tools.ActiveTool.OnMouseUp(e);
-        }
-        private void MouseController_OnMouseDownCoordinates(object sender, MouseMovementEventArgs e)
-        {
-            MaybeExecuteTool(e.NewPosition);
-        }
-
-        private bool MaybeExecuteTool(Coordinates newPosition)
-        {
-            if (MouseController.LeftMouseState == MouseButtonState.Pressed && !IsDraggingViewport() && ActiveDocument != null)
-            {
-                ExecuteTool(newPosition, MouseController.ClickedOnCanvas);
-                return true;
-            }
-            return false;
-        }
-
-        private bool IsDraggingViewport()
-        {
-            return _tools.ActiveTool is MoveViewportTool;
-        }
-
-        private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
-        {
-            _tools.ActiveTool.OnRecordingLeftMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (ActiveDocument != null)
-            {
-                ActiveDocument.PreviewLayer.Reset();
-            }
+            previewLayer.InvokeLayerBitmapChange();
         }
 
-        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
-        {
-            Tool selectedTool = _tools.ActiveTool;
-            selectedTool.OnStoppedRecordingMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (selectedTool is BitmapOperationTool operationTool && operationTool.RequiresPreviewLayer)
-            {
-                BitmapOperations.ApplyPreviewLayer();
-            }
-
-            HighlightPixels(MousePositionConverter.CurrentCoordinates);
-
-            startPosition = null;
-        }
-
         private void AdjustOffset(Coordinates newPosition, Layer previewLayer)
         {
             Coordinates start = newPosition - halfSize;

+ 32 - 168
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -2,23 +2,24 @@
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Undo;
 using PixiEditor.ViewModels.SubViewModels.Main;
 using SkiaSharp;
 using System;
 using System.Collections.Generic;
 using System.Windows;
-using System.Windows.Input;
 
 namespace PixiEditor.Models.Controllers
 {
     public class BitmapOperationsUtility
     {
-        private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+        public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
+
+        public BitmapManager Manager { get; set; }
 
+        public ToolsViewModel Tools { get; set; }
 
-        private SizeSetting sizeSetting;
+        private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
 
         public BitmapOperationsUtility(BitmapManager manager, ToolsViewModel tools)
         {
@@ -26,12 +27,6 @@ namespace PixiEditor.Models.Controllers
             Tools = tools;
         }
 
-        public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
-
-        public BitmapManager Manager { get; set; }
-
-        public ToolsViewModel Tools { get; set; }
-
         public void DeletePixels(Layer[] layers, Coordinates[] pixels)
         {
             if (Manager.ActiveDocument == null)
@@ -40,19 +35,11 @@ namespace PixiEditor.Models.Controllers
             }
 
             StorageBasedChange change = new StorageBasedChange(Manager.ActiveDocument, layers, true);
-            
 
-            // TODO: Fix
             BitmapPixelChanges changes = BitmapPixelChanges.FromSingleColoredArray(pixels, SKColors.Empty);
-            //Dictionary<Guid, SKColor[]> oldValues = BitmapUtils.GetPixelsForSelection(layers, pixels);
-            //LayerChange[] old = new LayerChange[layers.Length];
-            //LayerChange[] newChange = new LayerChange[layers.Length];
             for (int i = 0; i < layers.Length; i++)
             {
                 Guid guid = layers[i].LayerGuid;
-                //old[i] = new LayerChange(
-                    //BitmapPixelChanges.FromArrays(pixels, oldValues[layers[i].LayerGuid]), guid);
-                //newChange[i] = new LayerChange(changes, guid);
                 layers[i].SetPixels(changes);
             }
 
@@ -60,22 +47,37 @@ namespace PixiEditor.Models.Controllers
             Manager.ActiveDocument.UndoManager.AddUndoChange(change.ToChange(StorageBasedChange.BasicUndoProcess, args, "Delete selected pixels"));
         }
 
-        /// <summary>
-        ///     Executes tool Use() method with given parameters. NOTE: [0] is a start point, [^1] is latest.
-        /// </summary>
-        /// <param name="newPos">Most recent coordinates.</param>
-        /// <param name="mouseMove">Last mouse movement coordinates.</param>
-        /// <param name="tool">Tool to execute.</param>
-        public void ExecuteTool(Coordinates newPos, List<Coordinates> mouseMove, BitmapOperationTool tool)
+        public void UseTool(IReadOnlyList<Coordinates> recordedMouseMovement, BitmapOperationTool tool, SKColor color)
         {
-            if (Manager.ActiveDocument != null && tool != null)
+            if (Manager.ActiveDocument.Layers.Count == 0)
+                return;
+
+            if (!tool.RequiresPreviewLayer)
+            {
+                tool.Use(Manager.ActiveLayer, null, Manager.ActiveDocument.Layers, recordedMouseMovement, color);
+                BitmapChanged?.Invoke(this, null);
+            }
+            else
             {
-                if (Manager.ActiveDocument.Layers.Count == 0 || mouseMove.Count == 0)
+                UseToolOnPreviewLayer(recordedMouseMovement, tool.ClearPreviewLayerOnEachIteration);
+            }
+        }
+
+        private void UseToolOnPreviewLayer(IReadOnlyList<Coordinates> recordedMouseMovement, bool clearPreviewLayer)
+        {
+            if (recordedMouseMovement.Count > 0)
+            {
+                if (clearPreviewLayer)
                 {
-                    return;
+                    Manager.ActiveDocument.PreviewLayer.ClearCanvas();
                 }
 
-                UseTool(mouseMove, tool, Manager.PrimaryColor);
+                ((BitmapOperationTool)Tools.ActiveTool).Use(
+                    Manager.ActiveLayer,
+                    Manager.ActiveDocument.PreviewLayer,
+                    Manager.ActiveDocument.Layers,
+                    recordedMouseMovement,
+                    Manager.PrimaryColor);
             }
         }
 
@@ -95,147 +97,9 @@ namespace PixiEditor.Models.Controllers
                     previewLayer.OffsetY - activeLayer.OffsetY,
                     BlendingPaint
                 );
+
             Manager.ActiveLayer.InvokeLayerBitmapChange(dirtyRect);
-            // Don't forget about firing BitmapChanged
             BitmapChanged?.Invoke(this, null);
-            Manager.ActiveDocument.PreviewLayer.Reset();
-        }
-
-        private void UseTool(List<Coordinates> mouseMoveCords, BitmapOperationTool tool, SKColor color)
-        {
-            if (sizeSetting == null)
-            {
-                sizeSetting = tool.Toolbar.GetSetting<SizeSetting>("ToolSize");
-            }
-
-            int thickness = sizeSetting != null ? sizeSetting.Value : 1;
-
-            bool shiftDown = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
-
-            if (shiftDown && tool.UsesShift)
-            {
-                bool mouseInLine = DoCoordsFormLine(mouseMoveCords, thickness);
-
-                if (!mouseInLine)
-                {
-                    mouseMoveCords = GetSquareCoordiantes(mouseMoveCords);
-                }
-                else
-                {
-                    mouseMoveCords = GetLineCoordinates(mouseMoveCords, thickness);
-                }
-            }
-
-            if (!tool.RequiresPreviewLayer)
-            {
-                if (!Manager.ActiveDocument.PreviewLayer.IsReset)
-                    Manager.ActiveDocument.PreviewLayer.Reset();
-                tool.Use(Manager.ActiveLayer, mouseMoveCords, color);
-                BitmapChanged?.Invoke(this, null);
-            }
-            else
-            {
-                UseToolOnPreviewLayer(mouseMoveCords, tool.ClearPreviewLayerOnEachIteration);
-            }
-        }
-
-        private bool DoCoordsFormLine(List<Coordinates> coords, int thickness)
-        {
-            var p1 = coords[0];
-            var p2 = coords[^1];
-            //find delta and mirror to first quadrant
-            float dX = Math.Abs(p2.X - p1.X);
-            float dY = Math.Abs(p2.Y - p1.Y);
-
-            //normalize
-            float length = (float)Math.Sqrt(dX * dX + dY * dY);
-            if (length == 0)
-                return false;
-            dX = dX / length;
-            dY = dY / length;
-
-            return dX < 0.25f || dY < 0.25f; //angle < 15 deg or angle > 75 deg (sin 15 ~= 0.25)
-        }
-
-        private List<Coordinates> GetLineCoordinates(List<Coordinates> mouseMoveCords, int thickness)
-        {
-            int y = mouseMoveCords[0].Y;
-            int x = mouseMoveCords[0].X;
-
-            if (Math.Abs(mouseMoveCords[^1].X - mouseMoveCords[0].X) > Math.Abs(mouseMoveCords[^1].Y - mouseMoveCords[0].Y))
-            {
-                y = mouseMoveCords[^1].Y;
-            }
-            else
-            {
-                x = mouseMoveCords[^1].X;
-            }
-
-            mouseMoveCords[0] = new Coordinates(x, y);
-            return mouseMoveCords;
-        }
-
-        /// <summary>
-        ///     Extracts square from rectangle mouse drag, used to draw symmetric shapes.
-        /// </summary>
-        private List<Coordinates> GetSquareCoordiantes(List<Coordinates> mouseMoveCords)
-        {
-            var p1 = mouseMoveCords[0];
-            var p2 = mouseMoveCords[^1];
-
-            //find delta and mirror to first quadrant
-            var dX = Math.Abs(p2.X - p1.X);
-            var dY = Math.Abs(p2.Y - p1.Y);
-
-            float sqrt2 = (float)Math.Sqrt(2);
-            //vector of length 1 at 45 degrees;
-            float diagX, diagY;
-            diagX = diagY = 1 / sqrt2;
-
-            //dot product of delta and diag, returns length of [delta projected onto diag]
-            float projectedLength = diagX * dX + diagY * dY;
-            //project above onto axes
-            float axisLength = projectedLength / sqrt2;
-
-            //final coords
-            float x = -Math.Sign(p2.X - p1.X) * axisLength;
-            float y = -Math.Sign(p2.Y - p1.Y) * axisLength;
-            mouseMoveCords[0] = new Coordinates((int)x + p2.X, (int)y + p2.Y);
-            return mouseMoveCords;
-        }
-
-        private BitmapPixelChanges GetOldPixelsValues(Coordinates[] coordinates)
-        {
-            Dictionary<Coordinates, SKColor> values = new Dictionary<Coordinates, SKColor>();
-            //using (Manager.ActiveLayer.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
-            {
-                Coordinates[] relativeCoords = Manager.ActiveLayer.ConvertToRelativeCoordinates(coordinates);
-                for (int i = 0; i < coordinates.Length; i++)
-                {
-                    var cl = Manager.ActiveLayer.GetPixel(relativeCoords[i].X, relativeCoords[i].Y);
-                    values.Add(
-                        coordinates[i],
-                        cl);
-                }
-            }
-
-            return new BitmapPixelChanges(values);
-        }
-
-        private void UseToolOnPreviewLayer(List<Coordinates> mouseMove, bool clearPreviewLayer = true)
-        {
-            if (mouseMove.Count > 0)
-            {
-                if (clearPreviewLayer)
-                {
-                    Manager.ActiveDocument.PreviewLayer.ClearCanvas();
-                }
-
-                ((BitmapOperationTool)Tools.ActiveTool).Use(
-                    Manager.ActiveDocument.PreviewLayer,
-                    mouseMove,
-                    Manager.PrimaryColor);
-            }
         }
     }
 }

+ 15 - 0
PixiEditor/Models/Controllers/ICanvasInputTarget.cs

@@ -0,0 +1,15 @@
+using PixiEditor.Models.Tools;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Controllers
+{
+    public interface ICanvasInputTarget
+    {
+        void OnToolChange(Tool tool);
+        void OnKeyDown(Key key);
+        void OnKeyUp(Key key);
+        void OnLeftMouseButtonDown(double canvasPosX, double canvasPosY);
+        void OnLeftMouseButtonUp();
+        void OnMouseMove(double newCanvasX, double newCanvasY);
+    }
+}

+ 53 - 0
PixiEditor/Models/Controllers/MouseInputFilter.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Controllers
+{
+    internal class MouseInputFilter
+    {
+        public EventHandler<MouseButton> OnMouseDown;
+        public EventHandler OnMouseMove;
+        public EventHandler<MouseButton> OnMouseUp;
+
+
+        private Dictionary<MouseButton, MouseButtonState> buttonStates = new()
+        {
+            [MouseButton.Left] = MouseButtonState.Released,
+            [MouseButton.Right] = MouseButtonState.Released,
+            [MouseButton.Middle] = MouseButtonState.Released,
+        };
+
+        public void MouseDown(object args) => MouseDown(((MouseButtonEventArgs)args).ChangedButton);
+        public void MouseDown(MouseButton button)
+        {
+            if (button is MouseButton.XButton1 or MouseButton.XButton2)
+                return;
+            if (buttonStates[button] == MouseButtonState.Pressed)
+                return;
+            buttonStates[button] = MouseButtonState.Pressed;
+
+            OnMouseDown?.Invoke(this, button);
+        }
+
+        public void MouseMove(object args) => OnMouseMove?.Invoke(this, EventArgs.Empty);
+        public void MouseMove(MouseEventArgs args) => OnMouseMove?.Invoke(this, EventArgs.Empty);
+
+        public void MouseUp(object args) => MouseUp(((MouseButtonEventArgs)args).ChangedButton);
+        public void MouseUp(object sender, Point p, MouseButton button) => MouseUp(button);
+        public void MouseUp(MouseButton button)
+        {
+            if (button is MouseButton.XButton1 or MouseButton.XButton2)
+                return;
+            if (buttonStates[button] == MouseButtonState.Released)
+                return;
+            buttonStates[button] = MouseButtonState.Released;
+
+            OnMouseUp?.Invoke(this, button);
+        }
+    }
+}

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

@@ -1,94 +0,0 @@
-using PixiEditor.Models.Position;
-using System;
-using System.Collections.Generic;
-using System.Windows.Input;
-
-namespace PixiEditor.Models.Controllers
-{
-    public class MouseMovementController
-    {
-        public event EventHandler StartedRecordingChanges;
-
-        public event EventHandler<MouseEventArgs> OnMouseDown;
-        public event EventHandler<MouseMovementEventArgs> OnMouseDownCoordinates;
-
-        public event EventHandler<MouseEventArgs> OnMouseUp;
-
-        public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
-
-        public event EventHandler StoppedRecordingChanges;
-
-        public MouseButtonState LeftMouseState { get; private set; }
-
-        public List<Coordinates> LastMouseMoveCoordinates { get; set; } = new List<Coordinates>();
-
-        public bool IsRecordingChanges { get; private set; }
-
-        public bool ClickedOnCanvas { get; set; }
-
-
-        public void StartRecordingMouseMovementChanges(bool clickedOnCanvas)
-        {
-            if (IsRecordingChanges == false)
-            {
-                LastMouseMoveCoordinates.Clear();
-                IsRecordingChanges = true;
-                ClickedOnCanvas = clickedOnCanvas;
-                StartedRecordingChanges?.Invoke(this, EventArgs.Empty);
-            }
-        }
-
-        public void RecordMouseMovementChange(Coordinates mouseCoordinates)
-        {
-            if (IsRecordingChanges)
-            {
-                if (LastMouseMoveCoordinates.Count == 0 || mouseCoordinates != LastMouseMoveCoordinates[^1])
-                {
-                    LastMouseMoveCoordinates.Insert(0, mouseCoordinates);
-                    MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
-                }
-            }
-        }
-
-        /// <summary>
-        ///     Plain mouse move, does not affect mouse drag recordings.
-        /// </summary>
-        public void MouseMoved(Coordinates mouseCoordinates)
-        {
-            MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
-        }
-
-        /// <summary>
-        /// Plain mouse down, does not affect mouse recordings.
-        /// </summary>
-        public void MouseDown(MouseEventArgs args)
-        {
-            LeftMouseState = args.LeftButton;
-            OnMouseDown?.Invoke(this, args);
-        }
-
-        public void MouseDownCoordinates(Coordinates mouseCoordinates)
-        {
-            OnMouseDownCoordinates?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
-        }
-
-        /// <summary>
-        /// Plain mouse up, does not affect mouse recordings.
-        /// </summary>
-        public void MouseUp(MouseEventArgs args)
-        {
-            LeftMouseState = MouseButtonState.Released;
-            OnMouseUp?.Invoke(this, args);
-        }
-
-        public void StopRecordingMouseMovementChanges()
-        {
-            if (IsRecordingChanges)
-            {
-                IsRecordingChanges = false;
-                ClickedOnCanvas = false;
-                StoppedRecordingChanges?.Invoke(this, EventArgs.Empty);
-            }
-        }
-    }
-}

+ 0 - 14
PixiEditor/Models/Controllers/ReadonlyToolUtility.cs

@@ -1,14 +0,0 @@
-using System.Collections.Generic;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
-
-namespace PixiEditor.Models.Controllers
-{
-    public class ReadonlyToolUtility
-    {
-        public void ExecuteTool(List<Coordinates> mouseMove, ReadonlyTool tool)
-        {
-            tool.Use(mouseMove);
-        }
-    }
-}

+ 97 - 0
PixiEditor/Models/Controllers/ToolSession.cs

@@ -0,0 +1,97 @@
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using System;
+using System.Collections.Generic;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class ToolSession
+    {
+        private List<Coordinates> mouseMovement = new();
+        private bool ended = false;
+
+        public IReadOnlyList<Coordinates> MouseMovement => mouseMovement;
+        public Tool Tool { get; }
+
+        public bool IsCtrlDown { get; private set; }
+        public bool IsShiftDown { get; private set; }
+        public bool IsAltDown { get; private set; }
+
+        public ToolSession(
+            Tool tool,
+            double mouseXOnCanvas,
+            double mouseYOnCanvas,
+            IReadOnlyDictionary<Key, KeyStates> keyboardStates)
+        {
+            if (tool == null)
+                throw new ArgumentNullException(nameof(tool));
+            Tool = tool;
+
+            Tool.Session = this;
+            InvokeKeyboardEvents(keyboardStates);
+            mouseMovement.Add(new((int)Math.Floor(mouseXOnCanvas), (int)Math.Floor(mouseYOnCanvas)));
+            Tool.BeforeUse();
+        }
+
+        private void InvokeKeyboardEvents(IReadOnlyDictionary<Key, KeyStates> keyboardStates)
+        {
+            foreach (var pair in keyboardStates)
+            {
+                if (pair.Value == KeyStates.None)
+                    OnKeyUp(pair.Key);
+                else if (pair.Value == KeyStates.Down)
+                    OnKeyDown(pair.Key);
+            }
+        }
+
+        public void EndSession(IReadOnlyDictionary<Key, KeyStates> keyboardStates)
+        {
+            if (ended)
+                throw new Exception("Session has ended already");
+            ended = true;
+
+            Tool.AfterUse();
+            InvokeReleaseKeyboardEvents(keyboardStates);
+            Tool.Session = null;
+        }
+
+        private void InvokeReleaseKeyboardEvents(IReadOnlyDictionary<Key, KeyStates> keyboardStates)
+        {
+            foreach (var pair in keyboardStates)
+            {
+                if (pair.Value == KeyStates.Down)
+                    OnKeyUp(pair.Key);
+            }
+        }
+
+        public void OnKeyDown(Key key)
+        {
+            if (key == Key.LeftCtrl)
+                IsCtrlDown = true;
+            else if (key == Key.LeftShift)
+                IsShiftDown = true;
+            else if (key == Key.LeftAlt)
+                IsAltDown = true;
+
+            Tool.OnKeyDown(key);
+        }
+
+        public void OnKeyUp(Key key)
+        {
+            if (key == Key.LeftCtrl)
+                IsCtrlDown = false;
+            else if (key == Key.LeftShift)
+                IsShiftDown = false;
+            else if (key == Key.LeftAlt)
+                IsAltDown = false;
+
+            Tool.OnKeyUp(key);
+        }
+
+        public void OnPixelPositionChange(Coordinates pos)
+        {
+            mouseMovement.Add(pos);
+        }
+    }
+}

+ 140 - 0
PixiEditor/Models/Controllers/ToolSessionController.cs

@@ -0,0 +1,140 @@
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using System;
+using System.Collections.Generic;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class ToolSessionController : ICanvasInputTarget
+    {
+        public event EventHandler<MouseMovementEventArgs> PixelMousePositionChanged;
+        public event EventHandler<(double, double)> PreciseMousePositionChanged;
+        public event EventHandler<(Key, KeyStates)> KeyStateChanged;
+
+        public event EventHandler<ToolSession> SessionStarted;
+        public event EventHandler<ToolSession> SessionEnded;
+
+        public MouseButtonState LeftMouseState { get; private set; }
+
+        public bool IsShiftDown => keyboardState.ContainsKey(Key.LeftShift) ? keyboardState[Key.LeftShift] == KeyStates.Down : false;
+        public bool IsCtrlDown => keyboardState.ContainsKey(Key.LeftCtrl) ? keyboardState[Key.LeftCtrl] == KeyStates.Down : false;
+        public bool IsAltDown => keyboardState.ContainsKey(Key.LeftAlt) ? keyboardState[Key.LeftAlt] == KeyStates.Down : false;
+
+        public Coordinates LastPixelPosition => new(lastPixelX, lastPixelY);
+
+        private int lastPixelX;
+        private int lastPixelY;
+
+        private Dictionary<Key, KeyStates> keyboardState = new();
+        private Tool currentTool = null;
+        private ToolSession currentSession = null;
+
+        private void TryStartToolSession(Tool tool, double mouseXOnCanvas, double mouseYOnCanvas)
+        {
+            if (currentSession != null)
+                return;
+            currentSession = new(tool, mouseXOnCanvas, mouseYOnCanvas, keyboardState);
+            SessionStarted?.Invoke(this, currentSession);
+        }
+
+        private void TryStopToolSession()
+        {
+            if (currentSession == null)
+                return;
+            currentSession.EndSession(keyboardState);
+            SessionEnded?.Invoke(this, currentSession);
+            currentSession = null;
+        }
+
+        public void OnKeyDown(Key key)
+        {
+            key = ConvertRightKeys(key);
+            UpdateKeyState(key, KeyStates.Down);
+            currentSession?.OnKeyDown(key);
+            KeyStateChanged?.Invoke(this, (key, KeyStates.Down));
+        }
+
+        public void OnKeyUp(Key key)
+        {
+            key = ConvertRightKeys(key);
+            UpdateKeyState(key, KeyStates.None);
+            currentSession?.OnKeyUp(key);
+            KeyStateChanged?.Invoke(this, (key, KeyStates.None));
+        }
+
+        private void UpdateKeyState(Key key, KeyStates state)
+        {
+            key = ConvertRightKeys(key);
+            if (!keyboardState.ContainsKey(key))
+                keyboardState.Add(key, state);
+            else
+                keyboardState[key] = state;
+        }
+
+        private Key ConvertRightKeys(Key key)
+        {
+            if (key == Key.RightAlt)
+                return Key.LeftAlt;
+            if (key == Key.RightCtrl)
+                return Key.LeftCtrl;
+            if (key == Key.RightShift)
+                return Key.LeftShift;
+            return key;
+        }
+
+        public void ForceStopActiveSessionIfAny() => TryStopToolSession();
+
+        public void OnToolChange(Tool tool)
+        {
+            currentTool = tool;
+            TryStopToolSession();
+        }
+
+        public void OnMouseMove(double newCanvasX, double newCanvasY)
+        {
+            //update internal state
+
+            int newX = (int)Math.Floor(newCanvasX);
+            int newY = (int)Math.Floor(newCanvasY);
+            bool pixelPosChanged = false;
+            if (lastPixelX != newX || lastPixelY != newY)
+            {
+                lastPixelX = newX;
+                lastPixelY = newY;
+                pixelPosChanged = true;
+            }
+
+
+            //call session events
+            if (currentSession != null && pixelPosChanged)
+                currentSession.OnPixelPositionChange(new(newX, newY));
+
+            //call internal events
+            PreciseMousePositionChanged?.Invoke(this, (newCanvasX, newCanvasY));
+            if (pixelPosChanged)
+                PixelMousePositionChanged?.Invoke(this, new MouseMovementEventArgs(new Coordinates(newX, newY)));
+        }
+
+        public void OnLeftMouseButtonDown(double canvasPosX, double canvasPosY)
+        {
+            //update internal state
+            LeftMouseState = MouseButtonState.Pressed;
+
+            //call session events
+
+            if (currentTool == null)
+                throw new Exception("Current tool must not be null here");
+            TryStartToolSession(currentTool, canvasPosX, canvasPosY);
+        }
+
+        public void OnLeftMouseButtonUp()
+        {
+            //update internal state
+            LeftMouseState = MouseButtonState.Released;
+
+            //call session events
+            TryStopToolSession();
+        }
+    }
+}

+ 0 - 2
PixiEditor/Models/DataHolders/Document/Document.cs

@@ -195,8 +195,6 @@ namespace PixiEditor.Models.DataHolders
 
         private void SetAsActiveOnClick(object obj)
         {
-            XamlAccesibleViewModel.BitmapManager.MouseController.StopRecordingMouseMovementChanges();
-            //XamlAccesibleViewModel.BitmapManager.MouseController.StartRecordingMouseMovementChanges(true);
             if (XamlAccesibleViewModel.BitmapManager.ActiveDocument != this)
             {
                 XamlAccesibleViewModel.BitmapManager.ActiveDocument = this;

+ 16 - 20
PixiEditor/Models/Tools/BitmapOperationTool.cs

@@ -1,13 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
 using SkiaSharp;
+using System.Collections.Generic;
 
 namespace PixiEditor.Models.Tools
 {
@@ -18,33 +14,33 @@ namespace PixiEditor.Models.Tools
         public bool ClearPreviewLayerOnEachIteration { get; set; } = true;
 
         public bool UseDefaultUndoMethod { get; set; } = true;
-        public virtual bool UsesShift => true;
 
         private StorageBasedChange _change;
 
-        public abstract void Use(Layer layer, List<Coordinates> mouseMove, SKColor color);
+        public abstract void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color);
+
+        public override void BeforeUse()
+        {
+            if (UseDefaultUndoMethod)
+            {
+                Document doc = ViewModels.ViewModelMain.Current.BitmapManager.ActiveDocument;
+                _change = new StorageBasedChange(doc, new[] { doc.ActiveLayer }, true);
+            }
+        }
 
         /// <summary>
         /// Executes undo adding procedure.
         /// </summary>
         /// <param name="document">Active document</param>
         /// <remarks>When overriding, set UseDefaultUndoMethod to false.</remarks>
-        public override void AddUndoProcess(Document document)
+        public override void AfterUse()
         {
-            if (!UseDefaultUndoMethod) return;
-
+            if (!UseDefaultUndoMethod)
+                return;
+            var document = ViewModels.ViewModelMain.Current.BitmapManager.ActiveDocument;
             var args = new object[] { _change.Document };
             document.UndoManager.AddUndoChange(_change.ToChange(StorageBasedChange.BasicUndoProcess, args));
             _change = null;
         }
-
-        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
-        {
-            if (UseDefaultUndoMethod && e.LeftButton == MouseButtonState.Pressed)
-            {
-                Document doc = ViewModels.ViewModelMain.Current.BitmapManager.ActiveDocument;
-                _change = new StorageBasedChange(doc, new[] { doc.ActiveLayer }, true);
-            }
-        }
     }
 }

+ 4 - 4
PixiEditor/Models/Tools/ReadonlyTool.cs

@@ -1,10 +1,10 @@
-using System.Collections.Generic;
-using PixiEditor.Models.Position;
+using PixiEditor.Models.Position;
+using System.Collections.Generic;
 
 namespace PixiEditor.Models.Tools
 {
     public abstract class ReadonlyTool : Tool
     {
-        public abstract void Use(List<Coordinates> pixels);
+        public abstract void Use(IReadOnlyList<Coordinates> pixels);
     }
-}
+}

+ 0 - 5
PixiEditor/Models/Tools/ShapeTool.cs

@@ -1,10 +1,8 @@
-using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 using SkiaSharp;
 using System.Collections.Generic;
-using System.Linq;
 using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools
@@ -55,9 +53,6 @@ namespace PixiEditor.Models.Tools
             Toolbar = new BasicShapeToolbar();
         }
 
-        // TODO: Add cache for lines 31, 32 (hopefully it would speed up calculation)
-        public abstract override void Use(Layer layer, List<Coordinates> coordinates, SKColor color);
-
         public static void ThickenShape(Layer layer, SKColor color, IEnumerable<Coordinates> shape, int thickness)
         {
             foreach (Coordinates item in shape)

+ 16 - 55
PixiEditor/Models/Tools/Tool.cs

@@ -1,19 +1,11 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Windows;
-using System.Windows.Input;
-using PixiEditor.Helpers;
+using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using PixiEditor.Models.Undo;
-
-namespace PixiEditor.Models.Tools
+using PixiEditor.Models.Tools.ToolSettings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Tools
 {
     public abstract class Tool : NotifyableObject
     {
@@ -25,6 +17,8 @@ namespace PixiEditor.Models.Tools
 
         public virtual bool HideHighlight { get; }
 
+        public virtual bool RequiresPreciseMouseData { get; }
+
         public abstract string Tooltip { get; }
 
         public string ActionDisplay
@@ -51,53 +45,20 @@ namespace PixiEditor.Models.Tools
 
         public Toolbar Toolbar { get; set; } = new EmptyToolbar();
 
-        public bool CanStartOutsideCanvas { get; set; } = false;
+        public ToolSession Session { get; set; }
 
         private bool isActive;
         private string actionDisplay = string.Empty;
 
-        public virtual void OnMouseDown(MouseEventArgs e)
-        {
-        }
 
-        public virtual void AddUndoProcess(Document document)
-        {
-        }
+        public virtual void OnKeyDown(Key key) { }
 
-        public virtual void OnMouseUp(MouseEventArgs e)
-        {
-        }
+        public virtual void OnKeyUp(Key key) { }
 
-        public virtual void OnKeyDown(KeyEventArgs e)
-        {
-        }
+        public virtual void BeforeUse() { }
 
-        public virtual void OnKeyUp(KeyEventArgs e)
-        {
-        }
-
-        public virtual void OnStart(Coordinates clickPosition)
-        {
-        }
+        public virtual void AfterUse() { }
 
-        public virtual void OnRecordingLeftMouseDown(MouseEventArgs e)
-        {
-        }
-
-        public virtual void OnStoppedRecordingMouseUp(MouseEventArgs e)
-        {
-        }
-
-        public virtual void OnMouseMove(MouseEventArgs e)
-        {
-        }
-
-        public virtual void OnSelected()
-        {
-        }
-
-        public virtual void OnDeselected()
-        {
-        }
-    }
-}
+        public virtual void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown) { }
+    }
+}

+ 17 - 31
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Windows.Input;
-using System.Linq;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
+using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Colors;
 using PixiEditor.Models.Enums;
@@ -12,8 +7,9 @@ using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 using SkiaSharp;
+using System;
+using System.Collections.Generic;
 using System.Windows;
-using PixiEditor.Helpers;
 
 namespace PixiEditor.Models.Tools.Tools
 {
@@ -21,62 +17,52 @@ namespace PixiEditor.Models.Tools.Tools
     {
         private const float CorrectionFactor = 5f; // Initial correction factor
 
+        private readonly string defaultActionDisplay = "Draw on pixels to make them brighter. Hold Ctrl to darken.";
         private readonly List<Coordinates> pixelsVisited = new List<Coordinates>();
         private List<DoubleCoords> circleCache = new List<DoubleCoords>();
         private int cachedCircleSize = -1;
 
-        private string defaultActionDisplay = "Draw on pixels to make them brighter. Hold Ctrl to darken.";
         public BrightnessTool()
         {
             ActionDisplay = defaultActionDisplay;
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
         }
 
-        public override bool UsesShift => false;
-
         public override string Tooltip => "Makes pixels brighter or darker (U). Hold Ctrl to make pixels darker.";
 
         public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
 
-        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
-        {
-            base.OnRecordingLeftMouseDown(e);
-            pixelsVisited.Clear();
-        }
-
-        public override void OnKeyDown(KeyEventArgs e)
+        public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         {
-            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
-            {
+            if (!ctrlIsDown)
+                ActionDisplay = defaultActionDisplay;
+            else
                 ActionDisplay = "Draw on pixels to make them darker. Release Ctrl to brighten.";
-            }
         }
 
-        public override void OnKeyUp(KeyEventArgs e)
+        public override void BeforeUse()
         {
-            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
-            {
-                ActionDisplay = defaultActionDisplay;
-            }
+            base.BeforeUse();
+            pixelsVisited.Clear();
         }
 
-        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         {
             int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
             float correctionFactor = Toolbar.GetSetting<FloatSetting>("CorrectionFactor").Value;
             Mode = Toolbar.GetEnumSetting<BrightnessMode>("BrightnessMode").Value;
 
-            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
+            if (Session.IsCtrlDown)
             {
-                ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor);
+                ChangeBrightness(activeLayer, recordedMouseMovement[^1], toolSize, -correctionFactor);
             }
             else
             {
-                ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor);
+                ChangeBrightness(activeLayer, recordedMouseMovement[^1], toolSize, correctionFactor);
             }
         }
 
-        public void ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
+        private void ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
         {
             if (cachedCircleSize != toolSize)
                 UpdateCircleCache(toolSize);
@@ -112,7 +98,7 @@ namespace PixiEditor.Models.Tools.Tools
             layer.InvokeLayerBitmapChange(dirtyRect);
         }
 
-        public void UpdateCircleCache(int newCircleSize)
+        private void UpdateCircleCache(int newCircleSize)
         {
             cachedCircleSize = newCircleSize;
             DoubleCoords rect = CoordinatesCalculator.CalculateThicknessCenter(new Coordinates(0, 0), newCircleSize);

+ 30 - 37
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -5,16 +5,44 @@ using PixiEditor.Models.Tools.ToolSettings.Settings;
 using SkiaSharp;
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Linq;
 using System.Windows;
-using System.Windows.Input;
 using System.Windows.Media;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class CircleTool : ShapeTool
     {
+        private string defaultActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
+
+        public CircleTool()
+        {
+            ActionDisplay = defaultActionDisplay;
+        }
+
+        public override string Tooltip => "Draws circle on canvas (C). Hold Shift to draw even circle.";
+
+        public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+        {
+            if (shiftIsDown)
+                ActionDisplay = "Click and move mouse to draw an even circle.";
+            else
+                ActionDisplay = defaultActionDisplay;
+        }
+
+        public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
+        {
+            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
+            var hasFillColor = Toolbar.GetSetting<BoolSetting>("Fill").Value;
+            Color temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
+            SKColor fill = new SKColor(temp.R, temp.G, temp.B, temp.A);
+
+            var (start, end) = Session.IsShiftDown ?
+                CoordinatesHelper.GetSquareCoordiantes(recordedMouseMovement) :
+                (recordedMouseMovement[0], recordedMouseMovement[^1]);
+
+            DrawEllipseFromCoordinates(previewLayer, start, end, color, fill, thickness, hasFillColor);
+        }
 
         public static void DrawEllipseFromCoordinates(Layer layer, Coordinates first, Coordinates second,
             SKColor color, SKColor fillColor, int thickness, bool hasFillColor)
@@ -92,40 +120,5 @@ namespace PixiEditor.Models.Tools.Tools
                 }
             }
         }
-
-        private string defaultActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
-
-        public CircleTool()
-        {
-            ActionDisplay = defaultActionDisplay;
-        }
-
-        public override string Tooltip => "Draws circle on canvas (C). Hold Shift to draw even circle.";
-
-        public override void OnKeyDown(KeyEventArgs e)
-        {
-            if (e.Key is Key.LeftShift or Key.RightShift)
-            {
-                ActionDisplay = "Click and move mouse to draw an even circle.";
-            }
-        }
-
-        public override void OnKeyUp(KeyEventArgs e)
-        {
-            if (e.Key is Key.LeftShift or Key.RightShift)
-            {
-                ActionDisplay = defaultActionDisplay;
-            }
-        }
-
-        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
-        {
-            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
-            var hasFillColor = Toolbar.GetSetting<BoolSetting>("Fill").Value;
-            Color temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
-            SKColor fill = new SKColor(temp.R, temp.G, temp.B, temp.A);
-            DrawEllipseFromCoordinates(layer, coordinates[^1], coordinates[0], color, fill, thickness, hasFillColor);
-        }
-
     }
 }

+ 10 - 32
PixiEditor/Models/Tools/Tools/ColorPickerTool.cs

@@ -8,13 +8,10 @@ using PixiEditor.ViewModels;
 using SkiaSharp;
 using System;
 using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-using static System.Math;
 
 namespace PixiEditor.Models.Tools.Tools
 {
-    public class ColorPickerTool : ReadonlyTool
+    internal class ColorPickerTool : ReadonlyTool
     {
         private readonly DocumentProvider _docProvider;
         private readonly BitmapManager _bitmapManager;
@@ -29,11 +26,13 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override bool HideHighlight => true;
 
+        public override bool RequiresPreciseMouseData => true;
+
         public override string Tooltip => "Picks the primary color from the canvas. (O)";
 
-        public override void Use(List<Coordinates> coordinates)
+        public override void Use(IReadOnlyList<Coordinates> recordedMouseMovement)
         {
-            var coords = coordinates.First();
+            var coords = recordedMouseMovement[^1];
             var doc = _docProvider.GetDocument();
             if (coords.X < 0 || coords.Y < 0 || coords.X >= doc.Width || coords.Y >= doc.Height)
                 return;
@@ -45,12 +44,12 @@ namespace PixiEditor.Models.Tools.Tools
         {
             Layer referenceLayer = _docProvider.GetReferenceLayer();
 
-            if (referenceLayer != null && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
+            if (referenceLayer != null && Session.IsCtrlDown)
             {
                 double preciseX = _docProvider.GetDocument().MouseXOnCanvas;
                 double preciseY = _docProvider.GetDocument().MouseYOnCanvas;
 
-                if ((Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)))
+                if (Session.IsAltDown)
                 {
                     return GetCombinedColor(x, y, preciseX, preciseY);
                 }
@@ -112,32 +111,11 @@ namespace PixiEditor.Models.Tools.Tools
             }
         }
 
-        public override void OnKeyDown(KeyEventArgs e)
-        {
-            UpdateActionDisplay();
-        }
-
-        public override void OnKeyUp(KeyEventArgs e)
-        {
-            UpdateActionDisplay();
-        }
-
-        public override void OnSelected()
-        {
-            UpdateActionDisplay();
-        }
-
-        public override void OnDeselected()
-        {
-            _bitmapManager.OnlyReferenceLayer = false;
-            _bitmapManager.HideReferenceLayer = false;
-        }
-
-        private void UpdateActionDisplay()
+        public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         {
-            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
+            if (ctrlIsDown)
             {
-                if (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt))
+                if (altIsDown)
                 {
                     _bitmapManager.HideReferenceLayer = false;
                     _bitmapManager.OnlyReferenceLayer = false;

+ 6 - 9
PixiEditor/Models/Tools/Tools/EraserTool.cs

@@ -8,7 +8,7 @@ using System.Collections.Generic;
 
 namespace PixiEditor.Models.Tools.Tools
 {
-    public class EraserTool : BitmapOperationTool
+    internal class EraserTool : BitmapOperationTool
     {
         private readonly PenTool pen;
 
@@ -18,20 +18,17 @@ namespace PixiEditor.Models.Tools.Tools
             Toolbar = new BasicToolbar();
             pen = new PenTool(bitmapManager);
         }

-

-        public override bool UsesShift => false;

-

         public override string Tooltip => "Erasers color from pixel. (E)";
 
-        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         {
-            Erase(layer, coordinates, Toolbar.GetSetting<SizeSetting>("ToolSize").Value);
+            Erase(activeLayer, recordedMouseMovement, Toolbar.GetSetting<SizeSetting>("ToolSize").Value);
         }
 
-        public void Erase(Layer layer, List<Coordinates> coordinates, int toolSize)
+        public void Erase(Layer layer, IReadOnlyList<Coordinates> coordinates, int toolSize)
         {
-            Coordinates startingCords = coordinates.Count > 1 ? coordinates[1] : coordinates[0];
-            pen.Draw(layer, startingCords, coordinates[0], SKColors.Transparent, toolSize, false, null, SKBlendMode.Src);
+            Coordinates startingCords = coordinates.Count > 1 ? coordinates[^2] : coordinates[0];
+            pen.Draw(layer, startingCords, coordinates[^1], SKColors.Transparent, toolSize, false, null, SKBlendMode.Src);
         }
     }
 }

+ 7 - 8
PixiEditor/Models/Tools/Tools/FloodFillTool.cs

@@ -4,12 +4,11 @@ using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using SkiaSharp;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Windows;
 
 namespace PixiEditor.Models.Tools.Tools
 {
-    public class FloodFillTool : BitmapOperationTool
+    internal class FloodFillTool : BitmapOperationTool
     {
         private BitmapManager BitmapManager { get; }
         private SKPaint fillPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
@@ -22,17 +21,17 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override string Tooltip => "Fills area with color. (G)";
 
-        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         {
-            if (layer.IsReset)
+            if (activeLayer.IsReset)
             {
-                layer.DynamicResizeAbsolute(new(0, 0, BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
-                layer.LayerBitmap.SkiaSurface.Canvas.Clear(color);
-                layer.InvokeLayerBitmapChange();
+                activeLayer.DynamicResizeAbsolute(new(0, 0, BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
+                activeLayer.LayerBitmap.SkiaSurface.Canvas.Clear(color);
+                activeLayer.InvokeLayerBitmapChange();
             }
             else
             {
-                LinearFill(layer, coordinates[0], color);
+                LinearFill(activeLayer, recordedMouseMovement[^1], color);
             }
         }
 

+ 91 - 97
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Models.Layers;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
@@ -6,35 +7,96 @@ using SkiaSharp;
 using System;
 using System.Collections.Generic;
 using System.Windows;
-using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class LineTool : ShapeTool
     {
-        private readonly CircleTool circleTool;
         private List<Coordinates> linePoints = new List<Coordinates>();
         private SKPaint paint = new SKPaint() { Style = SKPaintStyle.Stroke };
 
         public bool AutomaticallyResizeCanvas { get; set; } = true;
 
-        public static List<Coordinates> GetBresenhamLine(Coordinates start, Coordinates end)
+        private string defaltActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
+
+        public LineTool()
         {
-            List<Coordinates> output = new List<Coordinates>();
-            CalculateBresenhamLine(start, end, output);
-            return output;
+            ActionDisplay = defaltActionDisplay;
+            Toolbar = new BasicToolbar();
         }
 
-        public static void CalculateBresenhamLine(Coordinates start, Coordinates end, List<Coordinates> output)
+        public override string Tooltip => "Draws line on canvas (L). Hold Shift to draw even line.";
+
+        public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         {
-            int x1 = start.X;
-            int x2 = end.X;
-            int y1 = start.Y;
-            int y2 = end.Y;
+            if (shiftIsDown)
+                ActionDisplay = "Click and move mouse to draw an even line.";
+            else
+                ActionDisplay = defaltActionDisplay;
+        }
+
+        public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
+        {
+            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
+
+            Coordinates start = recordedMouseMovement[0];
+            Coordinates end = recordedMouseMovement[^1];
+
+            if (Session.IsShiftDown)
+                (start, end) = CoordinatesHelper.GetSquareOrLineCoordinates(recordedMouseMovement);
+
+            DrawLine(previewLayer, start, end, color, thickness, SKBlendMode.Src);
+        }
 
+        public void DrawLine(
+            Layer layer, Coordinates start, Coordinates end, SKColor color, int thickness, SKBlendMode blendMode,
+            SKStrokeCap strokeCap = SKStrokeCap.Butt)
+        {
+            int x = start.X;
+            int y = start.Y;
+            int x1 = end.X;
+            int y1 = end.Y;
+
+            int dirtyX = Math.Min(x, x1) - thickness;
+            int dirtyY = Math.Min(y, y1) - thickness;
+
+            Int32Rect dirtyRect = new Int32Rect(
+                dirtyX,
+                dirtyY,
+                Math.Max(x1, x) + thickness - dirtyX,
+                Math.Max(y1, y) + thickness - dirtyY);
+            if (AutomaticallyResizeCanvas)
+            {
+                layer.DynamicResizeAbsolute(dirtyRect);
+            }
+
+            x -= layer.OffsetX;
+            y -= layer.OffsetY;
+            x1 -= layer.OffsetX;
+            y1 -= layer.OffsetY;
+
+            paint.StrokeWidth = thickness;
+            paint.Color = color;
+            paint.BlendMode = blendMode;
+            paint.StrokeCap = strokeCap;
+
+            if (thickness == 1)
+            {
+                DrawBresenhamLine(layer, x, y, x1, y1, paint);
+            }
+            else
+            {
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawLine(x, y, x1, y1, paint);
+            }
+
+            layer.InvokeLayerBitmapChange(dirtyRect);
+        }
+
+        private void DrawBresenhamLine(Layer layer, int x1, int y1, int x2, int y2, SKPaint paint)
+        {
             if (x1 == x2 && y1 == y2)
             {
-                output.Add(start);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x1, y1, paint);
                 return;
             }
 
@@ -63,7 +125,7 @@ namespace PixiEditor.Models.Tools.Tools
                 dy = y1 - y2;
             }
 
-            output.Add(new Coordinates(x, y));
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
 
             if (dx > dy)
             {
@@ -85,7 +147,7 @@ namespace PixiEditor.Models.Tools.Tools
                         x += xi;
                     }
 
-                    output.Add(new Coordinates(x, y));
+                    layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
                 }
             }
             else
@@ -108,97 +170,29 @@ namespace PixiEditor.Models.Tools.Tools
                         y += yi;
                     }
 
-                    output.Add(new Coordinates(x, y));
+                    layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
                 }
             }
         }
 
-        private string defaltActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
-
-        public LineTool()
-        {
-            ActionDisplay = defaltActionDisplay;
-            Toolbar = new BasicToolbar();
-            circleTool = new CircleTool();
-        }
-
-        public override string Tooltip => "Draws line on canvas (L). Hold Shift to draw even line.";
-
-        public override void OnKeyDown(KeyEventArgs e)
-        {
-            if (e.Key is Key.LeftShift or Key.RightShift)
-            {
-                ActionDisplay = "Click and move mouse to draw an even line.";
-            }
-        }
-
-        public override void OnKeyUp(KeyEventArgs e)
-        {
-            if (e.Key is Key.LeftShift or Key.RightShift)
-            {
-                ActionDisplay = defaltActionDisplay;
-            }
-        }
 
-        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        public static List<Coordinates> GetBresenhamLine(Coordinates start, Coordinates end)
         {
-            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
-
-            Coordinates start = coordinates[0];
-            Coordinates end = coordinates[^1];
-
-            DrawLine(layer, start, end, color, thickness, SKBlendMode.Src);
+            List<Coordinates> output = new List<Coordinates>();
+            CalculateBresenhamLine(start, end, output);
+            return output;
         }
 
-        public void DrawLine(
-            Layer layer, Coordinates start, Coordinates end, SKColor color, int thickness, SKBlendMode blendMode,
-            SKStrokeCap strokeCap = SKStrokeCap.Butt)
+        public static void CalculateBresenhamLine(Coordinates start, Coordinates end, List<Coordinates> output)
         {
-            int x = start.X;
-            int y = start.Y;
-            int x1 = end.X;
-            int y1 = end.Y;
-
-            int dirtyX = Math.Min(x, x1) - thickness;
-            int dirtyY = Math.Min(y, y1) - thickness;
-
-            Int32Rect dirtyRect = new Int32Rect(
-                dirtyX,
-                dirtyY,
-                Math.Max(x1, x) + thickness - dirtyX,
-                Math.Max(y1, y) + thickness - dirtyY);
-            if (AutomaticallyResizeCanvas)
-            {
-                layer.DynamicResizeAbsolute(dirtyRect);
-            }
-
-            x -= layer.OffsetX;
-            y -= layer.OffsetY;
-            x1 -= layer.OffsetX;
-            y1 -= layer.OffsetY;
-
-            paint.StrokeWidth = thickness;
-            paint.Color = color;
-            paint.BlendMode = blendMode;
-            paint.StrokeCap = strokeCap;
-
-            if (thickness == 1)
-            {
-                DrawBresenhamLine(layer, x, y, x1, y1, paint);
-            }
-            else
-            {
-                layer.LayerBitmap.SkiaSurface.Canvas.DrawLine(x, y, x1, y1, paint);
-            }
-
-            layer.InvokeLayerBitmapChange(dirtyRect);
-        }
+            int x1 = start.X;
+            int x2 = end.X;
+            int y1 = start.Y;
+            int y2 = end.Y;
 
-        private void DrawBresenhamLine(Layer layer, int x1, int y1, int x2, int y2, SKPaint paint)
-        {
             if (x1 == x2 && y1 == y2)
             {
-                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x1, y1, paint);
+                output.Add(start);
                 return;
             }
 
@@ -227,7 +221,7 @@ namespace PixiEditor.Models.Tools.Tools
                 dy = y1 - y2;
             }
 
-            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
+            output.Add(new Coordinates(x, y));
 
             if (dx > dy)
             {
@@ -249,7 +243,7 @@ namespace PixiEditor.Models.Tools.Tools
                         x += xi;
                     }
 
-                    layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
+                    output.Add(new Coordinates(x, y));
                 }
             }
             else
@@ -272,7 +266,7 @@ namespace PixiEditor.Models.Tools.Tools
                         y += yi;
                     }
 
-                    layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
+                    output.Add(new Coordinates(x, y));
                 }
             }
         }

+ 13 - 22
PixiEditor/Models/Tools/Tools/MagicWandTool.cs

@@ -12,11 +12,10 @@ using SkiaSharp;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Windows;
-using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools.Tools
 {
-    public class MagicWandTool : ReadonlyTool, ICachedDocumentTool
+    internal class MagicWandTool : ReadonlyTool, ICachedDocumentTool
     {
         private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
 
@@ -29,12 +28,19 @@ namespace PixiEditor.Models.Tools.Tools
 
         private Layer cachedDocument;
 
-        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        public MagicWandTool(BitmapManager manager)
         {
-            if (e.LeftButton != MouseButtonState.Pressed)
-            {
+            BitmapManager = manager;
+
+            Toolbar = new MagicWandToolbar();
+
+            ActionDisplay = "Click to flood the selection.";
+        }
+
+        public override void Use(IReadOnlyList<Coordinates> pixels)
+        {
+            if (pixels.Count > 1)
                 return;
-            }
 
             oldSelection = new ReadOnlyCollection<Coordinates>(ActiveSelection.SelectedPoints);
 
@@ -61,9 +67,7 @@ namespace PixiEditor.Models.Tools.Tools
 
             ToolCalculator.GetLinearFillAbsolute(
                    layer,
-                   new Coordinates(
-                       (int)document.MouseXOnCanvas,
-                       (int)document.MouseYOnCanvas),
+                   pixels[0],
                    BitmapManager.ActiveDocument.Width,
                    BitmapManager.ActiveDocument.Height,
                    SKColors.White,
@@ -74,19 +78,6 @@ namespace PixiEditor.Models.Tools.Tools
             SelectionHelpers.AddSelectionUndoStep(ViewModelMain.Current.BitmapManager.ActiveDocument, oldSelection, selectionType);
         }
 
-        public MagicWandTool(BitmapManager manager)
-        {
-            BitmapManager = manager;
-
-            Toolbar = new MagicWandToolbar();
-
-            ActionDisplay = "Click to flood the selection.";
-        }
-
-        public override void Use(List<Coordinates> pixels)
-        {
-        }
-
         public void DocumentChanged()
         {
             cachedDocument = null;

+ 53 - 67
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -3,24 +3,18 @@ using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.IO;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
-using PixiEditor.ViewModels;
 using SkiaSharp;
-using System;
 using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
-using Transform = PixiEditor.Models.ImageManipulation.Transform;
 
 namespace PixiEditor.Models.Tools.Tools
 {
-    public class MoveTool : BitmapOperationTool
+    internal class MoveTool : BitmapOperationTool
     {
         private static readonly SKPaint maskingPaint = new()
         {
@@ -38,7 +32,6 @@ namespace PixiEditor.Models.Tools.Tools
         private Surface previewLayerData;
 
         private List<Coordinates> moveStartSelectedPoints = null;
-        private Coordinates moveStartPos;
         private Int32Rect moveStartRect;
 
         private Coordinates lastDragDelta;
@@ -63,61 +56,21 @@ namespace PixiEditor.Models.Tools.Tools
 
         private BitmapManager BitmapManager { get; }
 
-        public override void OnKeyDown(KeyEventArgs e)
+        public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         {
-            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
-            {
+            if (ctrlIsDown)
                 ActionDisplay = "Hold mouse to move all layers.";
-            }
-        }
-
-        public override void OnKeyUp(KeyEventArgs e)
-        {
-            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
-            {
+            else
                 ActionDisplay = defaultActionDisplay;
-            }
         }
 
-        public override void AddUndoProcess(Document document)
-        {
-            var args = new object[] { change.Document };
-            document.UndoManager.AddUndoChange(change.ToChange(UndoProcess, args));
-            if (moveStartSelectedPoints != null)
-            {
-                SelectionHelpers.AddSelectionUndoStep(document, moveStartSelectedPoints, SelectionType.New);
-                document.UndoManager.SquashUndoChanges(3, "Move selected area");
-                moveStartSelectedPoints = null;
-            }
-            change = null;
-        }
-
-        private void UndoProcess(Layer[] layers, UndoLayer[] data, object[] args)
-        {
-            if (args.Length > 0 && args[0] is Document document)
-            {
-                for (int i = 0; i < layers.Length; i++)
-                {
-                    Layer layer = layers[i];
-                    document.Layers.RemoveAt(data[i].LayerIndex);
-
-                    document.Layers.Insert(data[i].LayerIndex, layer);
-                    if (data[i].IsActive)
-                    {
-                        document.SetMainActiveLayer(data[i].LayerIndex);
-                    }
-                }
-
-            }
-        }
-
-        public override void OnStart(Coordinates startPos)
+        public override void BeforeUse()
         {
             Document doc = BitmapManager.ActiveDocument;
             Selection selection = doc.ActiveSelection;
             bool anySelection = selection.SelectedPoints.Any();
 
-            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
+            if (Session.IsCtrlDown)
             {
                 affectedLayers = doc.Layers.Where(x => x.IsVisible).ToArray();
             }
@@ -129,10 +82,9 @@ namespace PixiEditor.Models.Tools.Tools
             change = new StorageBasedChange(doc, affectedLayers, true);
 
             Layer selLayer = selection.SelectionLayer;
-            moveStartRect = anySelection ? 
+            moveStartRect = anySelection ?
                 new(selLayer.OffsetX, selLayer.OffsetY, selLayer.Width, selLayer.Height) :
-                new (0, 0, doc.Width, doc.Height);
-            moveStartPos = startPos;
+                new(0, 0, doc.Width, doc.Height);
             lastDragDelta = new Coordinates(0, 0);
 
             previewLayerData?.Dispose();
@@ -193,7 +145,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
             using var selSnap = selLayer.LayerBitmap.SkiaSurface.Snapshot();
             Surface[] output = new Surface[draggedLayers.Length];
-            
+
             int count = 0;
             foreach (Layer layer in draggedLayers)
             {
@@ -209,17 +161,18 @@ namespace PixiEditor.Models.Tools.Tools
                 output[count] = portion;
                 count++;
 
-                layer.LayerBitmap.SkiaSurface.Canvas.DrawImage(selSnap, new SKRect(0, 0, selLayer.Width, selLayer.Height), 
-                    new SKRect(selLayer.OffsetX - layer.OffsetX, selLayer.OffsetY - layer.OffsetY, selLayer.OffsetX - layer.OffsetX + selLayer.Width, selLayer.OffsetY - layer.OffsetY + selLayer.Height), 
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawImage(selSnap, new SKRect(0, 0, selLayer.Width, selLayer.Height),
+                    new SKRect(selLayer.OffsetX - layer.OffsetX, selLayer.OffsetY - layer.OffsetY, selLayer.OffsetX - layer.OffsetX + selLayer.Width, selLayer.OffsetY - layer.OffsetY + selLayer.Height),
                     inverseMaskingPaint);
                 layer.InvokeLayerBitmapChange(new Int32Rect(selLayer.OffsetX, selLayer.OffsetY, selLayer.Width, selLayer.Height));
             }
             return output;
         }
 
-        public override void Use(Layer layer, List<Coordinates> mouseMove, SKColor color)
+        public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         {
-            Coordinates newPos = mouseMove[0];
+            Coordinates newPos = recordedMouseMovement[^1];
+            Coordinates moveStartPos = recordedMouseMovement[0];
             int dX = newPos.X - moveStartPos.X;
             int dY = newPos.Y - moveStartPos.Y;
             BitmapManager.ActiveDocument.ActiveSelection.TranslateSelection(dX - lastDragDelta.X, dY - lastDragDelta.Y);
@@ -230,21 +183,22 @@ namespace PixiEditor.Models.Tools.Tools
             int newY = moveStartRect.Y + dY;
 
             Int32Rect dirtyRect = new Int32Rect(newX, newY, moveStartRect.Width, moveStartRect.Height);
-            layer.DynamicResizeAbsolute(dirtyRect);
-            previewLayerData.SkiaSurface.Draw(layer.LayerBitmap.SkiaSurface.Canvas, newX - layer.OffsetX, newY - layer.OffsetY, Surface.ReplacingPaint);
-            layer.InvokeLayerBitmapChange(dirtyRect);
+            previewLayer.DynamicResizeAbsolute(dirtyRect);
+            previewLayerData.SkiaSurface.Draw(previewLayer.LayerBitmap.SkiaSurface.Canvas, newX - previewLayer.OffsetX, newY - previewLayer.OffsetY, Surface.ReplacingPaint);
+            previewLayer.InvokeLayerBitmapChange(dirtyRect);
         }
 
-        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
+        public override void AfterUse()
         {
-            base.OnStoppedRecordingMouseUp(e);
-
+            base.AfterUse();
             BitmapManager.ActiveDocument.PreviewLayer.ClearCanvas();
 
             ApplySurfacesToLayers(currentlyDragged, currentlyDraggedPositions, affectedLayers, new Coordinates(lastDragDelta.X, lastDragDelta.Y));
             foreach (var surface in currentlyDragged)
                 surface.Dispose();
             currentlyDragged = null;
+
+            SaveUndo(BitmapManager.ActiveDocument);
         }
 
         private static void ApplySurfacesToLayers(Surface[] surfaces, Coordinates[] startPositions, Layer[] layers, Coordinates delta)
@@ -263,5 +217,37 @@ namespace PixiEditor.Models.Tools.Tools
                 count++;
             }
         }
+
+        private void SaveUndo(Document document)
+        {
+            var args = new object[] { change.Document };
+            document.UndoManager.AddUndoChange(change.ToChange(UndoProcess, args));
+            if (moveStartSelectedPoints != null)
+            {
+                SelectionHelpers.AddSelectionUndoStep(document, moveStartSelectedPoints, SelectionType.New);
+                document.UndoManager.SquashUndoChanges(3, "Move selected area");
+                moveStartSelectedPoints = null;
+            }
+            change = null;
+        }
+
+        private void UndoProcess(Layer[] layers, UndoLayer[] data, object[] args)
+        {
+            if (args.Length > 0 && args[0] is Document document)
+            {
+                for (int i = 0; i < layers.Length; i++)
+                {
+                    Layer layer = layers[i];
+                    document.Layers.RemoveAt(data[i].LayerIndex);
+
+                    document.Layers.Insert(data[i].LayerIndex, layer);
+                    if (data[i].IsActive)
+                    {
+                        document.SetMainActiveLayer(data[i].LayerIndex);
+                    }
+                }
+
+            }
+        }
     }
 }

+ 4 - 5
PixiEditor/Models/Tools/Tools/MoveViewportTool.cs

@@ -1,5 +1,4 @@
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.Position;
+using PixiEditor.Models.Position;
 using PixiEditor.ViewModels.SubViewModels.Main;
 using System.Collections.Generic;
 using System.Windows.Input;
@@ -20,16 +19,16 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override bool HideHighlight => true;
         public override string Tooltip => "Move viewport. (H)";
-
+        /*
         public override void OnMouseUp(MouseEventArgs e)
         {
             if (e.MiddleButton == MouseButtonState.Pressed)
             {
                 ToolsViewModel.SetActiveTool(ToolsViewModel.LastActionTool);
             }
-        }
+        }*/
 
-        public override void Use(List<Coordinates> pixels)
+        public override void Use(IReadOnlyList<Coordinates> pixels)
         {
             // Implemented inside Zoombox.xaml.cs
         }

+ 12 - 13
PixiEditor/Models/Tools/Tools/PenTool.cs

@@ -13,7 +13,7 @@ using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools.Tools
 {
-    public class PenTool : ShapeTool
+    internal class PenTool : ShapeTool
     {
         public Brush Brush { get; set; }
         public List<Brush> Brushes { get; } = new List<Brush>();
@@ -50,34 +50,33 @@ namespace PixiEditor.Models.Tools.Tools
         }
 
         public override string Tooltip => "Standard brush. (B)";
-        public override bool UsesShift => false;
 
         public bool AutomaticallyResizeCanvas { get; set; } = true;
 
-        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        public override void BeforeUse()
         {
-            base.OnRecordingLeftMouseDown(e);
+            base.BeforeUse();
             changedPixelsindex = 0;
             lastChangedPixels = new Coordinates[3];
             confirmedPixels.Clear();
         }
 
-        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         {
-            Coordinates startingCords = coordinates.Count > 1 ? coordinates[1] : coordinates[0];
+            Coordinates startingCords = recordedMouseMovement.Count > 1 ? recordedMouseMovement[^2] : recordedMouseMovement[0];
             paint.Color = color;
             if (AutomaticallyResizeCanvas)
             {
-                int maxX = coordinates.Max(x => x.X);
-                int maxY = coordinates.Max(x => x.Y);
-                int minX = coordinates.Min(x => x.X);
-                int minY = coordinates.Min(x => x.Y);
-                layer.DynamicResizeAbsolute(new(minX, minY, maxX - minX + 1, maxX - minX + 1));
+                int maxX = recordedMouseMovement.Max(x => x.X);
+                int maxY = recordedMouseMovement.Max(x => x.Y);
+                int minX = recordedMouseMovement.Min(x => x.X);
+                int minY = recordedMouseMovement.Min(x => x.Y);
+                previewLayer.DynamicResizeAbsolute(new(minX, minY, maxX - minX + 1, maxX - minX + 1));
             }
             Draw(
-                layer,
+                previewLayer,
                 startingCords,
-                coordinates[0],
+                recordedMouseMovement[^1],
                 color,
                 toolSizeSetting.Value,
                 pixelPerfectSetting.Value,

+ 11 - 22
PixiEditor/Models/Tools/Tools/RectangleTool.cs

@@ -1,11 +1,11 @@
-using PixiEditor.Models.Layers;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using SkiaSharp;
 using System;
 using System.Collections.Generic;
 using System.Windows;
-using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools.Tools
 {
@@ -21,23 +21,15 @@ namespace PixiEditor.Models.Tools.Tools
 
         public bool Filled { get; set; } = false;
 
-        public override void OnKeyDown(KeyEventArgs e)
+        public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         {
-            if (e.Key is Key.LeftShift or Key.RightShift)
-            {
+            if (shiftIsDown)
                 ActionDisplay = "Click and move to draw a square.";
-            }
-        }
-
-        public override void OnKeyUp(KeyEventArgs e)
-        {
-            if (e.Key is Key.LeftShift or Key.RightShift)
-            {
+            else
                 ActionDisplay = defaultActionDisplay;
-            }
         }
 
-        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         {
             int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
             SKColor? fillColor = null;
@@ -46,12 +38,14 @@ namespace PixiEditor.Models.Tools.Tools
                 var temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
                 fillColor = new SKColor(temp.R, temp.G, temp.B, temp.A);
             }
-            CreateRectangle(layer, color, fillColor, coordinates, thickness);
+            CreateRectangle(previewLayer, color, fillColor, recordedMouseMovement, thickness);
         }
 
-        public void CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, List<Coordinates> coordinates, int thickness)
+        private void CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, IReadOnlyList<Coordinates> coordinates, int thickness)
         {
-            DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(coordinates[^1], coordinates[0]);
+            var (start, end) = Session.IsShiftDown ? CoordinatesHelper.GetSquareCoordiantes(coordinates) : (coordinates[0], coordinates[^1]);
+
+            DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
 
             int halfThickness = (int)Math.Ceiling(thickness / 2.0);
             Int32Rect dirtyRect = new Int32Rect(
@@ -83,10 +77,5 @@ namespace PixiEditor.Models.Tools.Tools
             }
             layer.InvokeLayerBitmapChange(dirtyRect);
         }
-
-        public void CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, Coordinates start, Coordinates end, int thickness)
-        {
-            CreateRectangle(layer, color, fillColor, new() { end, start }, thickness);
-        }
     }
 }

+ 12 - 16
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -1,25 +1,19 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Windows.Controls;
-using System.Windows.Input;
-using PixiEditor.Helpers;
+using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using PixiEditor.Models.Undo;
 using PixiEditor.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
 
 namespace PixiEditor.Models.Tools.Tools
 {
-    public class SelectTool : ReadonlyTool
+    internal class SelectTool : ReadonlyTool
     {
         private readonly RectangleTool rectangleTool;
         private readonly CircleTool circleTool;
@@ -43,15 +37,17 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override string Tooltip => "Selects area. (M)";
 
-        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        public override void BeforeUse()
         {
+            base.BeforeUse();
             SelectionType = Toolbar.GetEnumSetting<SelectionType>("SelectMode").Value;
 
             oldSelectedPoints = new ReadOnlyCollection<Coordinates>(ActiveSelection.SelectedPoints);
         }
 
-        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
+        public override void AfterUse()
         {
+            base.AfterUse();
             if (ActiveSelection.SelectedPoints.Count <= 1)
             {
                 // If we have not selected multiple points, clear the selection
@@ -61,7 +57,7 @@ namespace PixiEditor.Models.Tools.Tools
             SelectionHelpers.AddSelectionUndoStep(ViewModelMain.Current.BitmapManager.ActiveDocument, oldSelectedPoints, SelectionType);
         }
 
-        public override void Use(List<Coordinates> pixels)
+        public override void Use(IReadOnlyList<Coordinates> pixels)
         {
             Select(pixels, Toolbar.GetEnumSetting<SelectionShape>("SelectShape").Value);
         }
@@ -100,7 +96,7 @@ namespace PixiEditor.Models.Tools.Tools
             return GetRectangleSelectionForPoints(new Coordinates(0, 0), new Coordinates(document.Width - 1, document.Height - 1));
         }
 
-        private void Select(List<Coordinates> pixels, SelectionShape shape)
+        private void Select(IReadOnlyList<Coordinates> pixels, SelectionShape shape)
         {
             IEnumerable<Coordinates> selection;
 
@@ -126,4 +122,4 @@ namespace PixiEditor.Models.Tools.Tools
             BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
         }
     }
-}
+}

+ 6 - 7
PixiEditor/Models/Tools/Tools/ZoomTool.cs

@@ -5,14 +5,13 @@ using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools.Tools
 {
-    public class ZoomTool : ReadonlyTool
+    internal class ZoomTool : ReadonlyTool
     {
         private BitmapManager BitmapManager { get; }
         private string defaultActionDisplay = "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.";
 
         public ZoomTool(BitmapManager bitmapManager)
         {
-            CanStartOutsideCanvas = true;
             ActionDisplay = defaultActionDisplay;
             BitmapManager = bitmapManager;
         }
@@ -21,23 +20,23 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override string Tooltip => "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
 
-        public override void OnKeyDown(KeyEventArgs e)
+        public override void OnKeyDown(Key key)
         {
-            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
+            if (key is Key.LeftCtrl)
             {
                 ActionDisplay = "Click and move to zoom. Click to zoom out, release ctrl and click to zoom in.";
             }
         }
 
-        public override void OnKeyUp(KeyEventArgs e)
+        public override void OnKeyUp(Key key)
         {
-            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
+            if (key is Key.LeftCtrl)
             {
                 ActionDisplay = defaultActionDisplay;
             }
         }
 
-        public override void Use(List<Coordinates> pixels)
+        public override void Use(IReadOnlyList<Coordinates> pixels)
         {
             // Implemented inside Zoombox.xaml.cs
         }

+ 45 - 79
PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -1,9 +1,7 @@
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers.Shortcuts;
-using PixiEditor.Models.Position;
 using System;
-using System.Windows;
 using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
@@ -14,35 +12,32 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand MouseDownCommand { get; set; }
 
+        public RelayCommand MouseUpCommand { get; set; }
+
         public RelayCommand KeyDownCommand { get; set; }
 
         public RelayCommand KeyUpCommand { get; set; }
 
         private bool restoreToolOnKeyUp = false;
 
+        private MouseInputFilter filter = new();
+
         public IoViewModel(ViewModelMain owner)
             : base(owner)
         {
-            MouseMoveCommand = new RelayCommand(MouseMove);
-            MouseDownCommand = new RelayCommand(MouseDown);
-            KeyDownCommand = new RelayCommand(KeyDown);
-            KeyUpCommand = new RelayCommand(KeyUp);
-        }
-
-        public void MouseHook_OnMouseUp(object sender, Point p, MouseButton button)
-        {
-            GlobalMouseHook.OnMouseUp -= MouseHook_OnMouseUp;
-            if (button == MouseButton.Left)
-            {
-                Owner.BitmapManager.MouseController.StopRecordingMouseMovementChanges();
-            }
-
-            Owner.BitmapManager.MouseController.MouseUp(new MouseEventArgs(
-                Mouse.PrimaryDevice,
-                (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            MouseDownCommand = new RelayCommand(filter.MouseDown);
+            MouseMoveCommand = new RelayCommand(filter.MouseMove);
+            MouseUpCommand = new RelayCommand(filter.MouseUp);
+            GlobalMouseHook.OnMouseUp += filter.MouseUp;
+            KeyDownCommand = new RelayCommand(OnKeyDown);
+            KeyUpCommand = new RelayCommand(OnKeyUp);
+
+            filter.OnMouseDown += OnMouseDown;
+            filter.OnMouseMove += OnMouseMove;
+            filter.OnMouseUp += OnMouseUp;
         }
 
-        public void KeyDown(object parameter)
+        private void OnKeyDown(object parameter)
         {
             KeyEventArgs args = (KeyEventArgs)parameter;
             if (args.IsRepeat && !restoreToolOnKeyUp && Owner.ShortcutController.LastShortcut != null &&
@@ -53,82 +48,53 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
 
             Owner.ShortcutController.KeyPressed(args.Key, Keyboard.Modifiers);
-            Owner.ToolsSubViewModel.ActiveTool.OnKeyDown(args);
+
+            if (Owner.BitmapManager.ActiveDocument != null)
+                Owner.BitmapManager.InputTarget.OnKeyDown(args.Key);
         }
 
-        private void MouseDown(object parameter)
+        private void OnKeyUp(object parameter)
         {
-            if (Owner.BitmapManager.ActiveDocument == null || Owner.BitmapManager.ActiveDocument.Layers.Count == 0)
+            KeyEventArgs args = (KeyEventArgs)parameter;
+            if (restoreToolOnKeyUp && Owner.ShortcutController.LastShortcut != null &&
+                Owner.ShortcutController.LastShortcut.ShortcutKey == args.Key)
             {
-                return;
+                restoreToolOnKeyUp = false;
+                Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
+                ShortcutController.BlockShortcutExecution = false;
             }
 
-            if (Mouse.LeftButton == MouseButtonState.Pressed)
+            if (Owner.BitmapManager.ActiveDocument != null)
+                Owner.BitmapManager.InputTarget.OnKeyUp(args.Key);
+        }
+
+        private void OnMouseDown(object sender, MouseButton button)
+        {
+            if (button == MouseButton.Left)
             {
                 BitmapManager bitmapManager = Owner.BitmapManager;
                 var activeDocument = bitmapManager.ActiveDocument;
-                if (!bitmapManager.MouseController.IsRecordingChanges)
-                {
-                    bool clickedOnCanvas = activeDocument.MouseXOnCanvas >= 0 &&
-                        activeDocument.MouseXOnCanvas <= activeDocument.Width &&
-                        activeDocument.MouseYOnCanvas >= 0 &&
-                        activeDocument.MouseYOnCanvas <= activeDocument.Height;
-                    bitmapManager.MouseController.StartRecordingMouseMovementChanges(clickedOnCanvas);
-                    bitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
-                }
-            }
-
-            Owner.BitmapManager.MouseController.MouseDown(new MouseEventArgs(
-                Mouse.PrimaryDevice,
-                (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+                if (activeDocument == null)
+                    return;
 
-            Coordinates cords = new Coordinates(
-                (int)Owner.BitmapManager.ActiveDocument.MouseXOnCanvas,
-                (int)Owner.BitmapManager.ActiveDocument.MouseYOnCanvas);
-            Owner.BitmapManager.MouseController.MouseDownCoordinates(cords);
-
-            // 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.
-            // This seems better than maintaining a global listener indefinitely.
-            GlobalMouseHook.OnMouseUp += MouseHook_OnMouseUp;
+                bitmapManager.InputTarget.OnLeftMouseButtonDown(activeDocument.MouseXOnCanvas, activeDocument.MouseYOnCanvas);
+            }
         }
 
-        /// <summary>
-        ///     Method connected with command, it executes tool "activity".
-        /// </summary>
-        /// <param name="parameter">CommandParameter.</param>
-        private void MouseMove(object parameter)
+        private void OnMouseMove(object sender, EventArgs args)
         {
-            if (Owner.BitmapManager.ActiveDocument == null)
-            {
+            var activeDocument = Owner.BitmapManager.ActiveDocument;
+            if (activeDocument == null)
                 return;
-            }
-
-            Coordinates cords = new Coordinates(
-                (int)Owner.BitmapManager.ActiveDocument.MouseXOnCanvas,
-                (int)Owner.BitmapManager.ActiveDocument.MouseYOnCanvas);
-            MousePositionConverter.CurrentCoordinates = cords;
-
-            if (Owner.BitmapManager.MouseController.IsRecordingChanges && Mouse.LeftButton == MouseButtonState.Pressed)
-            {
-                Owner.BitmapManager.MouseController.RecordMouseMovementChange(cords);
-            }
-
-            Owner.BitmapManager.MouseController.MouseMoved(cords);
+            Owner.BitmapManager.InputTarget.OnMouseMove(activeDocument.MouseXOnCanvas, activeDocument.MouseYOnCanvas);
         }
 
-        private void KeyUp(object parameter)
+        private void OnMouseUp(object sender, MouseButton button)
         {
-            KeyEventArgs args = (KeyEventArgs)parameter;
-            if (restoreToolOnKeyUp && Owner.ShortcutController.LastShortcut != null &&
-                Owner.ShortcutController.LastShortcut.ShortcutKey == args.Key)
-            {
-                restoreToolOnKeyUp = false;
-                Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
-                ShortcutController.BlockShortcutExecution = false;
-            }
-
-            Owner.ToolsSubViewModel.ActiveTool.OnKeyUp(args);
+            if (Owner.BitmapManager.ActiveDocument == null)
+                return;
+            if (button == MouseButton.Left)
+                Owner.BitmapManager.InputTarget.OnLeftMouseButtonUp();
         }
     }
 }

+ 4 - 6
PixiEditor/ViewModels/SubViewModels/Main/StylusViewModel.cs

@@ -1,10 +1,8 @@
-using System.Windows;
-using System.Windows.Input;
-using GalaSoft.MvvmLight.CommandWpf;
-using PixiEditor.Models.Position;
+using GalaSoft.MvvmLight.CommandWpf;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.UserPreferences;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -75,7 +73,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private void StylusOutOfRange(StylusEventArgs e)
         {
-            Owner.BitmapManager.HighlightPixels(new Coordinates(-1, -1));
+            Owner.BitmapManager.UpdateHighlightIfNecessary(true);
         }
 
         private void StylusSystemGesture(StylusSystemGestureEventArgs e)
@@ -110,4 +108,4 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
     }
-}
+}

+ 11 - 15
PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -1,19 +1,14 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Reflection;
-using System.Windows.Input;
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.Helpers;
-using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Events;
-using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -41,7 +36,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public Tool ActiveTool
         {
             get => activeTool;
-            set => SetProperty(ref activeTool, value);
+            private set => SetProperty(ref activeTool, value);
         }
 
         public int ToolSize
@@ -54,7 +49,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 if (ActiveTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is SizeSetting toolSize)
                 {
                     toolSize.Value = value;
-                    Owner.BitmapManager.HighlightPixels(MousePositionConverter.CurrentCoordinates);
+                    Owner.BitmapManager.UpdateHighlightIfNecessary();
                 }
             }
         }
@@ -90,15 +85,16 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             if (ActiveTool != null)
             {
                 activeTool.IsActive = false;
-                ActiveTool.OnDeselected();
             }
 
             LastActionTool = ActiveTool;
             ActiveTool = tool;
             SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
 
+            //update new tool
+            Owner.BitmapManager.UpdateActionDisplay();
+
             tool.IsActive = true;
-            ActiveTool.OnSelected();
             SetToolCursor(tool.GetType());
 
             if (Owner.StylusSubViewModel != null)
@@ -188,4 +184,4 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
     }
-}
+}

+ 3 - 16
PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs

@@ -1,11 +1,6 @@
-using System;
-using System.IO;
-using System.Linq;
-using PixiEditor.Helpers;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Tools;
+using PixiEditor.Helpers;
 using PixiEditor.Models.Undo;
+using System.IO;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -28,14 +23,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             ClearUndoTempDirectory();
         }
 
-        public void TriggerNewUndoChange(Tool selectedTool)
-        {
-            var activeDoc = Owner.BitmapManager.ActiveDocument;
-            if (activeDoc is null) return;
-
-            selectedTool.AddUndoProcess(activeDoc);
-        }
-
         /// <summary>
         ///     Redo last action.
         /// </summary>
@@ -86,4 +73,4 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             return Owner.BitmapManager.ActiveDocument?.UndoManager.CanRedo ?? false;
         }
     }
-}
+}

+ 4 - 9
PixiEditor/ViewModels/ViewModelMain.cs

@@ -132,7 +132,6 @@ namespace PixiEditor.ViewModels
             Preferences.Init();
             BitmapManager = services.GetRequiredService<BitmapManager>();
             BitmapManager.BitmapOperations.BitmapChanged += BitmapUtility_BitmapChanged;
-            BitmapManager.MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
             BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
 
             SelectionSubViewModel = services.GetService<SelectionViewModel>();
@@ -142,6 +141,7 @@ namespace PixiEditor.ViewModels
 
             FileSubViewModel = services.GetService<FileViewModel>();
             ToolsSubViewModel = services.GetService<ToolsViewModel>();
+            ToolsSubViewModel.SelectedToolChanged += BitmapManager_SelectedToolChanged;
             ToolsSubViewModel?.SetupTools(services);
 
             IoSubViewModel = services.GetService<IoViewModel>();
@@ -218,8 +218,6 @@ namespace PixiEditor.ViewModels
                         new Shortcut(Key.F1, MiscSubViewModel.OpenShortcutWindowCommand, "Open the shortcut window", true)));
 
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
-
-            ToolsSubViewModel.SelectedToolChanged += BitmapManager_SelectedToolChanged;
         }
 
         /// <summary>
@@ -249,10 +247,12 @@ namespace PixiEditor.ViewModels
 
         private void BitmapManager_SelectedToolChanged(object sender, SelectedToolEventArgs e)
         {
-            e.OldTool.PropertyChanged -= SelectedTool_PropertyChanged;
+            if (e.OldTool != null)
+                e.OldTool.PropertyChanged -= SelectedTool_PropertyChanged;
             e.NewTool.PropertyChanged += SelectedTool_PropertyChanged;
 
             NotifyToolActionDisplayChanged();
+            BitmapManager.InputTarget.OnToolChange(e.NewTool);
         }
 
         private void SelectedTool_PropertyChanged(object sender, PropertyChangedEventArgs e)
@@ -360,11 +360,6 @@ namespace PixiEditor.ViewModels
             BitmapManager.ActiveDocument.CenterViewportTrigger.Execute(this, new Size(BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
         }
 
-        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
-        {
-            UndoSubViewModel.TriggerNewUndoChange(ToolsSubViewModel.ActiveTool);
-        }
-
         private void BitmapUtility_BitmapChanged(object sender, BitmapChangedEventArgs e)
         {
             BitmapManager.ActiveDocument.ChangesSaved = false;

+ 1 - 0
PixiEditor/Views/MainWindow.xaml

@@ -285,6 +285,7 @@
                                         MiddleMouseClickedCommand="{Binding XamlAccesibleViewModel.ToolsSubViewModel.SelectToolCommand}"
                                         MouseMoveCommand="{Binding XamlAccesibleViewModel.IoSubViewModel.MouseMoveCommand}"
                                         MouseDownCommand="{Binding XamlAccesibleViewModel.IoSubViewModel.MouseDownCommand}"
+                                        MouseUpCommand="{Binding XamlAccesibleViewModel.IoSubViewModel.MouseUpCommand}"
                                         MouseXOnCanvas="{Binding MouseXOnCanvas, Mode=TwoWay}"
                                         MouseYOnCanvas="{Binding MouseYOnCanvas, Mode=TwoWay}"
                                         StylusButtonDownCommand="{Binding XamlAccesibleViewModel.StylusSubViewModel.StylusDownCommand}"

+ 5 - 2
PixiEditor/Views/UserControls/DrawingViewPort.xaml

@@ -24,10 +24,13 @@
                    UseTouchGestures="{Binding UseTouchGestures, ElementName=uc}">
         <i:Interaction.Triggers>
             <i:EventTrigger EventName="MouseMove">
-                <i:InvokeCommandAction Command="{Binding MouseMoveCommand, ElementName=uc}" />
+                <cmd:EventToCommand Command="{Binding MouseMoveCommand, ElementName=uc}" PassEventArgsToCommand="True" />
             </i:EventTrigger>
             <i:EventTrigger EventName="MouseDown">
-                <i:InvokeCommandAction Command="{Binding MouseDownCommand, ElementName=uc}"/>
+                <cmd:EventToCommand Command="{Binding MouseDownCommand, ElementName=uc}" PassEventArgsToCommand="True" />
+            </i:EventTrigger>
+            <i:EventTrigger EventName="MouseUp">
+                <cmd:EventToCommand Command="{Binding MouseUpCommand, ElementName=uc}" PassEventArgsToCommand="True" />
             </i:EventTrigger>
             <i:EventTrigger EventName="PreviewMouseDown">
                 <i:InvokeCommandAction Command="{Binding PreviewMouseDownCommand, ElementName=uc}"/>

+ 10 - 1
PixiEditor/Views/UserControls/DrawingViewPort.xaml.cs

@@ -20,7 +20,10 @@ namespace PixiEditor.Views.UserControls
 
         public static readonly DependencyProperty MouseDownCommandProperty =
             DependencyProperty.Register(nameof(MouseDownCommand), typeof(ICommand), typeof(DrawingViewPort), new PropertyMetadata(default(ICommand)));
-        
+
+        public static readonly DependencyProperty MouseUpCommandProperty =
+            DependencyProperty.Register(nameof(MouseUpCommand), typeof(ICommand), typeof(DrawingViewPort), new PropertyMetadata(default(ICommand)));
+
         public static readonly DependencyProperty StylusButtonDownCommandProperty =
             DependencyProperty.Register(nameof(StylusButtonDownCommand), typeof(ICommand), typeof(DrawingViewPort), new PropertyMetadata(default(ICommand)));
 
@@ -77,6 +80,12 @@ namespace PixiEditor.Views.UserControls
             set => SetValue(MouseDownCommandProperty, value);
         }
 
+        public ICommand MouseUpCommand
+        {
+            get => (ICommand)GetValue(MouseUpCommandProperty);
+            set => SetValue(MouseUpCommandProperty, value);
+        }
+
         public ICommand StylusButtonDownCommand
         {
             get => (ICommand)GetValue(StylusButtonDownCommandProperty);

+ 2 - 2
PixiEditor/Views/UserControls/Layers/LayerGroupControl.xaml.cs

@@ -61,12 +61,12 @@ namespace PixiEditor.Views.UserControls.Layers
             LayerGroupControl control = (LayerGroupControl)d;
             if (e.OldValue is LayersViewModel oldVm && oldVm != e.NewValue)
             {
-                oldVm.Owner.BitmapManager.MouseController.StoppedRecordingChanges -= control.MouseController_StoppedRecordingChanges;
+                oldVm.Owner.BitmapManager.StopUsingTool -= control.MouseController_StoppedRecordingChanges;
             }
 
             if (e.NewValue is LayersViewModel vm)
             {
-                vm.Owner.BitmapManager.MouseController.StoppedRecordingChanges += control.MouseController_StoppedRecordingChanges;
+                vm.Owner.BitmapManager.StopUsingTool += control.MouseController_StoppedRecordingChanges;
             }
         }
 

+ 7 - 7
PixiEditorTests/ModelsTests/ControllersTests/MouseMovementControllerTests.cs

@@ -10,7 +10,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatStartRecordingMouseMovChangesStartsRecordingAndInvokesEvent()
         {
             bool eventInvoked = false;
-            MouseMovementController controller = new MouseMovementController();
+            ToolSessionController controller = new ToolSessionController();
             controller.StartedRecordingChanges += (sender, e) => eventInvoked = true;
 
             controller.StartRecordingMouseMovementChanges(false);
@@ -23,7 +23,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         [Fact]
         public void TestThatRecordMouseMovementChangeRecordsMouseMovementChange()
         {
-            MouseMovementController controller = new MouseMovementController();
+            ToolSessionController controller = new ToolSessionController();
             controller.StartRecordingMouseMovementChanges(false);
             controller.RecordMouseMovementChange(new Coordinates(5, 5));
 
@@ -39,14 +39,14 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             Coordinates position = new Coordinates(5, 5);
             MouseMovementEventArgs args = new MouseMovementEventArgs(default(Coordinates));
 
-            MouseMovementController controller = new MouseMovementController();
-            controller.MousePositionChanged += (s, e) =>
+            ToolSessionController controller = new ToolSessionController();
+            controller.PreciseMousePositionChanged += (s, e) =>
             {
                 eventRaised = true;
                 args = e;
             };
 
-            controller.MouseMoved(position);
+            controller.OnMouseMove(position);
 
             Assert.True(eventRaised);
             Assert.Equal(position, args.NewPosition);
@@ -55,7 +55,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         [Fact]
         public void TestStopRecordingChangesStopsRecording()
         {
-            MouseMovementController controller = new MouseMovementController();
+            ToolSessionController controller = new ToolSessionController();
 
             controller.StartRecordingMouseMovementChanges(true);
             controller.StopRecordingMouseMovementChanges();
@@ -67,7 +67,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         [Fact]
         public void TestThatRecordChangesNotRecords()
         {
-            MouseMovementController controller = new MouseMovementController();
+            ToolSessionController controller = new ToolSessionController();
             controller.RecordMouseMovementChange(new Coordinates(5, 10));
 
             Assert.False(controller.IsRecordingChanges);