Browse Source

Fixed almost all warnings - only 18 left

ArtemK123 4 years ago
parent
commit
4b7070c1ae
96 changed files with 3824 additions and 3748 deletions
  1. 10 6
      Custom.ruleset
  2. 1 1
      PixiEditor.UpdateInstaller/App.xaml.cs
  3. 0 0
      PixiEditor/Exceptions/ArrayLengthMismatchException.cs
  4. 7 7
      PixiEditor/Helpers/Behaviours/AllowableCharactersTextBoxBehavior.cs
  5. 7 7
      PixiEditor/Helpers/Behaviours/HintTextBehavior.cs
  6. 98 91
      PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs
  7. 24 24
      PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs
  8. 18 16
      PixiEditor/Helpers/Extensions/DictionaryHelper.cs
  9. 128 127
      PixiEditor/Helpers/GlobalMouseHook.cs
  10. 37 49
      PixiEditor/Helpers/RelayCommand.cs
  11. 110 113
      PixiEditor/Models/Colors/ExColor.cs
  12. 18 0
      PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs
  13. 243 256
      PixiEditor/Models/Controllers/BitmapManager.cs
  14. 21 33
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  15. 86 90
      PixiEditor/Models/Controllers/ClipboardController.cs
  16. 18 0
      PixiEditor/Models/Controllers/LayersChangedEventArgs.cs
  17. 7 18
      PixiEditor/Models/Controllers/MouseMovementController.cs
  18. 12 0
      PixiEditor/Models/Controllers/MouseMovementEventArgs.cs
  19. 36 36
      PixiEditor/Models/Controllers/PixelChangesController.cs
  20. 1 2
      PixiEditor/Models/Controllers/Shortcuts/Shortcut.cs
  21. 71 71
      PixiEditor/Models/Controllers/UndoManager.cs
  22. 56 61
      PixiEditor/Models/DataHolders/BitmapPixelChanges.cs
  23. 95 78
      PixiEditor/Models/DataHolders/Change.cs
  24. 284 284
      PixiEditor/Models/DataHolders/Document.cs
  25. 21 0
      PixiEditor/Models/DataHolders/DocumentSizeChangedEventArgs.cs
  26. 92 93
      PixiEditor/Models/IO/Exporter.cs
  27. 2 2
      PixiEditor/Models/IO/Importer.cs
  28. 65 66
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  29. 52 51
      PixiEditor/Models/ImageManipulation/Morphology.cs
  30. 3 3
      PixiEditor/Models/ImageManipulation/Transform.cs
  31. 415 422
      PixiEditor/Models/Layers/Layer.cs
  32. 71 71
      PixiEditor/Models/Layers/SerializableLayer.cs
  33. 8 8
      PixiEditor/Models/Position/Coordinates.cs
  34. 15 22
      PixiEditor/Models/Position/CoordinatesCalculator.cs
  35. 13 13
      PixiEditor/Models/Position/DoubleCords.cs
  36. 5 5
      PixiEditor/Models/Position/MousePositionConverter.cs
  37. 10 5
      PixiEditor/Models/Tools/ShapeTool.cs
  38. 42 38
      PixiEditor/Models/Tools/ToolSettings/Settings/FloatSetting.cs
  39. 0 20
      PixiEditor/Models/Tools/ToolSettings/Settings/Setting.cs
  40. 22 0
      PixiEditor/Models/Tools/ToolSettings/Settings/Setting{T}.cs
  41. 1 1
      PixiEditor/Models/Tools/ToolSettings/Toolbars/BasicToolbar.cs
  42. 2 2
      PixiEditor/Models/Tools/ToolSettings/Toolbars/Toolbar.cs
  43. 79 74
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  44. 18 18
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  45. 1 2
      PixiEditor/Models/Tools/Tools/ColorPickerTool.cs
  46. 11 10
      PixiEditor/Models/Tools/Tools/LineTool.cs
  47. 17 16
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  48. 21 21
      PixiEditor/Models/Tools/Tools/RectangleTool.cs
  49. 80 80
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  50. 60 60
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  51. 15 14
      PixiEditor/NotifyableObject.cs
  52. 6 6
      PixiEditor/Properties/AssemblyInfo.cs
  53. 1 1
      PixiEditor/ViewModels/ImportFilePopupViewModel.cs
  54. 1 1
      PixiEditor/ViewModels/SaveFilePopupViewModel.cs
  55. 25 25
      PixiEditor/ViewModels/ViewModelBase.cs
  56. 338 346
      PixiEditor/ViewModels/ViewModelMain.cs
  57. 39 40
      PixiEditor/Views/AnchorPointPicker.xaml.cs
  58. 60 57
      PixiEditor/Views/ConfirmationPopup.xaml.cs
  59. 1 1
      PixiEditor/Views/EditableTextBlock.xaml
  60. 14 17
      PixiEditor/Views/EditableTextBlock.xaml.cs
  61. 1 1
      PixiEditor/Views/ImportFilePopup.xaml.cs
  62. 1 1
      PixiEditor/Views/LayerItem.xaml.cs
  63. 1 1
      PixiEditor/Views/MainDrawingPanel.xaml
  64. 162 165
      PixiEditor/Views/MainDrawingPanel.xaml.cs
  65. 1 1
      PixiEditor/Views/MainWindow.xaml
  66. 2 2
      PixiEditor/Views/MainWindow.xaml.cs
  67. 42 43
      PixiEditor/Views/MenuButton.xaml.cs
  68. 1 1
      PixiEditor/Views/NewFilePopup.xaml.cs
  69. 70 61
      PixiEditor/Views/NumberInput.xaml.cs
  70. 1 1
      PixiEditor/Views/PopupTemplate.xaml.cs
  71. 62 59
      PixiEditor/Views/ResizeCanvasPopup.xaml.cs
  72. 1 1
      PixiEditor/Views/ResizeDocumentPopup.xaml.cs
  73. 66 64
      PixiEditor/Views/Rotatebox.xaml.cs
  74. 1 1
      PixiEditor/Views/SaveFilePopup.xaml.cs
  75. 1 1
      PixiEditor/Views/SizeInput.xaml
  76. 69 71
      PixiEditor/Views/SizeInput.xaml.cs
  77. 1 1
      PixiEditor/Views/SizePicker.xaml.cs
  78. 0 11
      PixiEditorTests/ModelsTests/ControllersTests/BitmapManagerTests.cs
  79. 8 4
      PixiEditorTests/ModelsTests/ControllersTests/ClipboardControllerTests.cs
  80. 21 0
      PixiEditorTests/ModelsTests/ControllersTests/MockedSinglePixelPen.cs
  81. 1 1
      PixiEditorTests/ModelsTests/ControllersTests/MouseMovementControllerTests.cs
  82. 6 3
      PixiEditorTests/ModelsTests/ControllersTests/PixelChangesControllerTests.cs
  83. 1 20
      PixiEditorTests/ModelsTests/ControllersTests/ReadonlyUtilityTests.cs
  84. 80 81
      PixiEditorTests/ModelsTests/ControllersTests/ShortcutControllerTests.cs
  85. 7 0
      PixiEditorTests/ModelsTests/ControllersTests/TestPropertyClass.cs
  86. 23 0
      PixiEditorTests/ModelsTests/ControllersTests/TestReadonlyTool.cs
  87. 7 9
      PixiEditorTests/ModelsTests/ControllersTests/UndoManagerTests.cs
  88. 2 2
      PixiEditorTests/ModelsTests/DataHoldersTests/BitmapPixelChangesTests.cs
  89. 6 2
      PixiEditorTests/ModelsTests/DataHoldersTests/DocumentTests.cs
  90. 2 1
      PixiEditorTests/ModelsTests/DataHoldersTests/SerializableDocumentTests.cs
  91. 0 1
      PixiEditorTests/ModelsTests/IO/ImporterTests.cs
  92. 1 0
      PixiEditorTests/ModelsTests/ImageManipulationTests/BitmapUtilsTests.cs
  93. 130 129
      PixiEditorTests/ModelsTests/LayersTests/LayerTests.cs
  94. 1 0
      PixiEditorTests/ModelsTests/ToolsTests/BrightnessToolTests.cs
  95. 29 27
      PixiEditorTests/ModelsTests/ToolsTests/RectangleToolTests.cs
  96. 2 1
      PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

+ 10 - 6
Custom.ruleset

@@ -1,8 +1,12 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <RuleSet Name="Name" Description="Description" ToolsVersion="16.0">
-    <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
-        <Rule Id="SA1101" Action="None" />
-        <Rule Id="SA1633" Action="None" />
-        <Rule Id="SA0001" Action="None" />
-    </Rules>
+  <Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
+    <Rule Id="CA1303" Action="None" />
+  </Rules>
+  <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
+    <Rule Id="SA0001" Action="None" />
+    <Rule Id="SA1101" Action="None" />
+    <Rule Id="SA1413" Action="None" />
+    <Rule Id="SA1633" Action="None" />
+  </Rules>
 </RuleSet>

+ 1 - 1
PixiEditor.UpdateInstaller/App.xaml.cs

@@ -3,7 +3,7 @@
 namespace PixiEditor.UpdateInstaller
 {
     /// <summary>
-    ///     Interaction logic for App.xaml
+    ///     Interaction logic for App.xaml.
     /// </summary>
     public partial class App : Application
     {

+ 0 - 0
PixiEditor/Exceptions/LengthMismatchException.cs → PixiEditor/Exceptions/ArrayLengthMismatchException.cs


+ 7 - 7
PixiEditor/Helpers/Behaviours/AllowableCharactersTextBoxBehavior.cs

@@ -42,6 +42,13 @@ namespace PixiEditor.Helpers.Behaviours
             DataObject.AddPastingHandler(AssociatedObject, OnPaste);
         }
 
+        protected override void OnDetaching()
+        {
+            base.OnDetaching();
+            AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
+            DataObject.RemovePastingHandler(AssociatedObject, OnPaste);
+        }
+
         private void OnPaste(object sender, DataObjectPastingEventArgs e)
         {
             if (e.DataObject.GetDataPresent(DataFormats.Text))
@@ -64,13 +71,6 @@ namespace PixiEditor.Helpers.Behaviours
             e.Handled = !IsValid(e.Text, false);
         }
 
-        protected override void OnDetaching()
-        {
-            base.OnDetaching();
-            AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
-            DataObject.RemovePastingHandler(AssociatedObject, OnPaste);
-        }
-
         private bool IsValid(string newText, bool paste)
         {
             return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(newText, RegularExpression);

+ 7 - 7
PixiEditor/Helpers/Behaviours/HintTextBehavior.cs

@@ -32,6 +32,13 @@ namespace PixiEditor.Helpers.Behaviours
             SetHint(true);
         }
 
+        protected override void OnDetaching()
+        {
+            base.OnDetaching();
+            AssociatedObject.LostFocus -= AssociatedObject_LostFocus;
+            AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
+        }
+
         private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
         {
             if (string.IsNullOrEmpty(AssociatedObject.Text))
@@ -61,12 +68,5 @@ namespace PixiEditor.Helpers.Behaviours
                 AssociatedObject.Foreground = textColor;
             }
         }
-
-        protected override void OnDetaching()
-        {
-            base.OnDetaching();
-            AssociatedObject.LostFocus -= AssociatedObject_LostFocus;
-            AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
-        }
     }
 }

+ 98 - 91
PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -1,110 +1,117 @@
-using System.Text.RegularExpressions;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using System.Windows.Interactivity;
-
-namespace PixiEditor.Helpers.Behaviours
-{
-    internal class TextBoxFocusBehavior : Behavior<TextBox>
-    {
-        // Using a DependencyProperty as the backing store for FillSize.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty FillSizeProperty =
-            DependencyProperty.Register("FillSize", typeof(bool), typeof(TextBoxFocusBehavior),
-                new PropertyMetadata(false));
+using System.Text.RegularExpressions;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Interactivity;
 
-        private string oldText; // Value of textbox before editing
-        private bool valueConverted; // This bool is used to avoid double convertion if enter is hitted
-
-        public bool FillSize
-        {
-            get => (bool)GetValue(FillSizeProperty);
-            set => SetValue(FillSizeProperty, value);
-        }
-
-        // Converts number to proper format if enter is clicked and moves focus to next object
-        private void AssociatedObject_KeyUp(object sender, KeyEventArgs e)
-        {
+namespace PixiEditor.Helpers.Behaviours
+{
+    internal class TextBoxFocusBehavior : Behavior<TextBox>
+    {
+        // Using a DependencyProperty as the backing store for FillSize.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty FillSizeProperty =
+            DependencyProperty.Register(
+                "FillSize",
+                typeof(bool),
+                typeof(TextBoxFocusBehavior),
+                new PropertyMetadata(false));
+
+        private string oldText; // Value of textbox before editing
+        private bool valueConverted; // This bool is used to avoid double convertion if enter is hitted
+
+        public bool FillSize
+        {
+            get => (bool)GetValue(FillSizeProperty);
+            set => SetValue(FillSizeProperty, value);
+        }
+
+        protected override void OnAttached()
+        {
+            base.OnAttached();
+            AssociatedObject.GotKeyboardFocus += AssociatedObjectGotKeyboardFocus;
+            AssociatedObject.GotMouseCapture += AssociatedObjectGotMouseCapture;
+            AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectPreviewMouseLeftButtonDown;
+            AssociatedObject.LostKeyboardFocus += AssociatedObject_LostKeyboardFocus;
+            AssociatedObject.KeyUp += AssociatedObject_KeyUp;
+        }
+
+        protected override void OnDetaching()
+        {
+            base.OnDetaching();
+            AssociatedObject.GotKeyboardFocus -= AssociatedObjectGotKeyboardFocus;
+            AssociatedObject.GotMouseCapture -= AssociatedObjectGotMouseCapture;
+            AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectPreviewMouseLeftButtonDown;
+            AssociatedObject.LostKeyboardFocus -= AssociatedObject_LostKeyboardFocus;
+            AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
+        }
+
+        // Converts number to proper format if enter is clicked and moves focus to next object
+        private void AssociatedObject_KeyUp(object sender, KeyEventArgs e)
+        {
             if (e.Key != Key.Enter)
             {
                 return;
             }
 
-            ConvertValue();
-            AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
-        }
-
-        protected override void OnAttached()
-        {
-            base.OnAttached();
-            AssociatedObject.GotKeyboardFocus += AssociatedObjectGotKeyboardFocus;
-            AssociatedObject.GotMouseCapture += AssociatedObjectGotMouseCapture;
-            AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectPreviewMouseLeftButtonDown;
-            AssociatedObject.LostKeyboardFocus += AssociatedObject_LostKeyboardFocus;
-            AssociatedObject.KeyUp += AssociatedObject_KeyUp;
-        }
-
-        protected override void OnDetaching()
-        {
-            base.OnDetaching();
-            AssociatedObject.GotKeyboardFocus -= AssociatedObjectGotKeyboardFocus;
-            AssociatedObject.GotMouseCapture -= AssociatedObjectGotMouseCapture;
-            AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectPreviewMouseLeftButtonDown;
-            AssociatedObject.LostKeyboardFocus -= AssociatedObject_LostKeyboardFocus;
-            AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
-        }
-
-        private void AssociatedObjectGotKeyboardFocus(object sender,
-            KeyboardFocusChangedEventArgs e)
-        {
-            AssociatedObject.SelectAll();
-            if (FillSize)
-            {
-                valueConverted = false;
-                oldText = AssociatedObject.Text; // Sets old value when keyboard is focused on object
-            }
-        }
-
-        private void AssociatedObjectGotMouseCapture(object sender,
-            MouseEventArgs e)
-        {
-            AssociatedObject.SelectAll();
-        }
-
-        private void AssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
-        {
-            if (!AssociatedObject.IsKeyboardFocusWithin)
-            {
-                AssociatedObject.Focus();
-                e.Handled = true;
-            }
-        }
-
-        private void AssociatedObject_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
-        {
-            ConvertValue();
-        }
-
-        /// <summary>
-        ///     Converts number from textbox to format "number px" ex. "15 px"
-        /// </summary>
-        private void ConvertValue()
-        {
+            ConvertValue();
+            AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
+        }
+
+        private void AssociatedObjectGotKeyboardFocus(
+            object sender,
+            KeyboardFocusChangedEventArgs e)
+        {
+            AssociatedObject.SelectAll();
+            if (FillSize)
+            {
+                valueConverted = false;
+                oldText = AssociatedObject.Text; // Sets old value when keyboard is focused on object
+            }
+        }
+
+        private void AssociatedObjectGotMouseCapture(
+            object sender,
+            MouseEventArgs e)
+        {
+            AssociatedObject.SelectAll();
+        }
+
+        private void AssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            if (!AssociatedObject.IsKeyboardFocusWithin)
+            {
+                AssociatedObject.Focus();
+                e.Handled = true;
+            }
+        }
+
+        private void AssociatedObject_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
+        {
+            ConvertValue();
+        }
+
+        /// <summary>
+        ///     Converts number from textbox to format "number px" ex. "15 px".
+        /// </summary>
+        private void ConvertValue()
+        {
             if (valueConverted || FillSize == false)
             {
                 return;
             }
 
-            if (int.TryParse(Regex.Replace(AssociatedObject.Text, "\\p{L}", ""), out int result) && result > 0)
+            if (int.TryParse(Regex.Replace(AssociatedObject.Text, "\\p{L}", string.Empty), out int result) && result > 0)
             {
                 AssociatedObject.Text = $"{AssociatedObject.Text} px";
             }
-            else // If text in textbox isn't number, set it to old value
+
+            // If text in textbox isn't number, set it to old value
+            else
             {
                 AssociatedObject.Text = oldText;
             }
 
-            valueConverted = true;
-        }
-    }
+            valueConverted = true;
+        }
+    }
 }

+ 24 - 24
PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs

@@ -1,34 +1,34 @@
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Windows.Data;
-
-namespace PixiEditor.Helpers
-{
-    [ValueConversion(typeof(string), typeof(int))]
-    internal class ToolSizeToIntConverter : IValueConverter
-    {
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            return string.Format("{0} {1}", value, "px");
-        }
-
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
-        {
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers
+{
+    [ValueConversion(typeof(string), typeof(int))]
+    internal class ToolSizeToIntConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return string.Format("{0} {1}", value, "px");
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
             if (string.IsNullOrWhiteSpace(value as string))
             {
                 return null;
             }
 
-            string slicedString = value.ToString().Split(' ').First();
-            slicedString = Regex.Replace(slicedString, "\\p{L}", "");
-            if (slicedString == "")
+            string slicedString = value.ToString().Split(' ').First();
+            slicedString = Regex.Replace(slicedString, "\\p{L}", string.Empty);
+            if (slicedString == string.Empty)
             {
                 return null;
             }
 
-            return int.Parse(slicedString);
-        }
-    }
+            return int.Parse(slicedString);
+        }
+    }
 }

+ 18 - 16
PixiEditor/Helpers/Extensions/DictionaryHelper.cs

@@ -1,21 +1,23 @@
-using System.Collections.Generic;
-
-namespace PixiEditor.Helpers.Extensions
-{
-    public static class DictionaryHelper
-    {
-        public static void AddRangeOverride<TKey, TValue>(this IDictionary<TKey, TValue> dict,
-            IDictionary<TKey, TValue> dictToAdd)
-        {
+using System.Collections.Generic;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class DictionaryHelper
+    {
+        public static void AddRangeOverride<TKey, TValue>(
+            this IDictionary<TKey, TValue> dict,
+            IDictionary<TKey, TValue> dictToAdd)
+        {
             foreach (KeyValuePair<TKey, TValue> item in dictToAdd)
             {
                 dict[item.Key] = item.Value;
             }
-        }
-
-        public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dict,
-            IDictionary<TKey, TValue> dictToAdd)
-        {
+        }
+
+        public static void AddRangeNewOnly<TKey, TValue>(
+            this IDictionary<TKey, TValue> dict,
+            IDictionary<TKey, TValue> dictToAdd)
+        {
             foreach (KeyValuePair<TKey, TValue> item in dictToAdd)
             {
                 if (!dict.ContainsKey(item.Key))
@@ -23,6 +25,6 @@ namespace PixiEditor.Helpers.Extensions
                     dict.Add(item.Key, item.Value);
                 }
             }
-        }
-    }
+        }
+    }
 }

+ 128 - 127
PixiEditor/Helpers/GlobalMouseHook.cs

@@ -1,134 +1,135 @@
-using System;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.InteropServices;
-using System.Windows;
-
-namespace PixiEditor.Helpers
-{
-    // see https://stackoverflow.com/questions/22659925/how-to-capture-mouseup-event-outside-the-wpf-window
-    [ExcludeFromCodeCoverage]
-    public static class GlobalMouseHook
-    {
-        private const int WhMouseLl = 14;
-        private const int WmLbuttonup = 0x0202;
-        private static int mouseHookHandle;
-        private static HookProc mouseDelegate;
-
-        private static event MouseUpEventHandler MouseUp;
-
-        public static event MouseUpEventHandler OnMouseUp
-        {
-            add
-            {
-                Subscribe();
-                MouseUp += value;
-            }
-
-            remove
-            {
-                MouseUp -= value;
-                Unsubscribe();
-            }
-        }
-
-        public static void RaiseMouseUp()
-        {
-            MouseUp?.Invoke(default, default);
-        }
-
-        private static void Unsubscribe()
-        {
-            if (mouseHookHandle != 0)
-            {
-                int result = UnhookWindowsHookEx(mouseHookHandle);
-                mouseHookHandle = 0;
-                mouseDelegate = null;
-                if (result == 0)
-                {
-                    int errorCode = Marshal.GetLastWin32Error();
-                    throw new Win32Exception(errorCode);
-                }
-            }
-        }
-
-        private static void Subscribe()
-        {
-            if (mouseHookHandle == 0)
-            {
-                mouseDelegate = MouseHookProc;
-                mouseHookHandle = SetWindowsHookEx(
-                    WhMouseLl,
-                    mouseDelegate,
-                    GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
-                    0);
-                if (mouseHookHandle == 0)
-                {
-                    int errorCode = Marshal.GetLastWin32Error();
-                    throw new Win32Exception(errorCode);
-                }
-            }
-        }
-
-        private static int MouseHookProc(int nCode, int wParam, IntPtr lParam)
-        {
-            if (nCode >= 0)
-            {
-                Msllhookstruct mouseHookStruct = (Msllhookstruct)Marshal.PtrToStructure(lParam, typeof(Msllhookstruct));
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+namespace PixiEditor.Helpers
+{
+    public delegate void MouseUpEventHandler(object sender, Point p);
+
+    // see https://stackoverflow.com/questions/22659925/how-to-capture-mouseup-event-outside-the-wpf-window
+    [ExcludeFromCodeCoverage]
+    public static class GlobalMouseHook
+    {
+        private const int WhMouseLl = 14;
+        private const int WmLbuttonup = 0x0202;
+        private static int mouseHookHandle;
+        private static HookProc mouseDelegate;
+
+        private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
+
+        public static event MouseUpEventHandler OnMouseUp
+        {
+            add
+            {
+                Subscribe();
+                MouseUp += value;
+            }
+
+            remove
+            {
+                MouseUp -= value;
+                Unsubscribe();
+            }
+        }
+
+        private static event MouseUpEventHandler MouseUp;
+
+        public static void RaiseMouseUp()
+        {
+            MouseUp?.Invoke(default, default);
+        }
+
+        private static void Unsubscribe()
+        {
+            if (mouseHookHandle != 0)
+            {
+                int result = UnhookWindowsHookEx(mouseHookHandle);
+                mouseHookHandle = 0;
+                mouseDelegate = null;
+                if (result == 0)
+                {
+                    int errorCode = Marshal.GetLastWin32Error();
+                    throw new Win32Exception(errorCode);
+                }
+            }
+        }
+
+        private static void Subscribe()
+        {
+            if (mouseHookHandle == 0)
+            {
+                mouseDelegate = MouseHookProc;
+                mouseHookHandle = SetWindowsHookEx(
+                    WhMouseLl,
+                    mouseDelegate,
+                    GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
+                    0);
+                if (mouseHookHandle == 0)
+                {
+                    int errorCode = Marshal.GetLastWin32Error();
+                    throw new Win32Exception(errorCode);
+                }
+            }
+        }
+
+        private static int MouseHookProc(int nCode, int wParam, IntPtr lParam)
+        {
+            if (nCode >= 0)
+            {
+                Msllhookstruct mouseHookStruct = (Msllhookstruct)Marshal.PtrToStructure(lParam, typeof(Msllhookstruct));
                 if (wParam == WmLbuttonup)
                 {
                     if (MouseUp != null)
                     {
-                        MouseUp.Invoke(null, new System.Windows.Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y));
+                        MouseUp.Invoke(null, new System.Windows.Point(mouseHookStruct.Pt.X, mouseHookStruct.Pt.Y));
                     }
                 }
-            }
-
-            return CallNextHookEx(mouseHookHandle, nCode, wParam, lParam);
-        }
-
-        [DllImport("user32.dll",
-            CharSet = CharSet.Auto,
-            CallingConvention = CallingConvention.StdCall,
-            SetLastError = true)]
-        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
-
-        [DllImport(
-            "user32.dll",
-            CharSet = CharSet.Auto,
-            CallingConvention = CallingConvention.StdCall,
-            SetLastError = true)]
-        private static extern int UnhookWindowsHookEx(int idHook);
-
-        [DllImport(
-            "user32.dll",
-            CharSet = CharSet.Auto,
-            CallingConvention = CallingConvention.StdCall)]
-        private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
-
-        [DllImport("kernel32.dll")]
-        private static extern IntPtr GetModuleHandle(string name);
-
-        private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
-
-        [StructLayout(LayoutKind.Sequential)]
-        private struct Point
-        {
-            public readonly int x;
-            public readonly int y;
-        }
-
-        [StructLayout(LayoutKind.Sequential)]
-        private struct Msllhookstruct
-        {
-            public readonly Point pt;
-            public readonly uint mouseData;
-            public readonly uint flags;
-            public readonly uint time;
-            public readonly IntPtr dwExtraInfo;
-        }
-    }
-
-    public delegate void MouseUpEventHandler(object sender, Point p);
+            }
+
+            return CallNextHookEx(mouseHookHandle, nCode, wParam, lParam);
+        }
+
+        [DllImport(
+            "user32.dll",
+            CharSet = CharSet.Auto,
+            CallingConvention = CallingConvention.StdCall,
+            SetLastError = true)]
+        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
+
+        [DllImport(
+            "user32.dll",
+            CharSet = CharSet.Auto,
+            CallingConvention = CallingConvention.StdCall,
+            SetLastError = true)]
+        private static extern int UnhookWindowsHookEx(int idHook);
+
+        [DllImport(
+            "user32.dll",
+            CharSet = CharSet.Auto,
+            CallingConvention = CallingConvention.StdCall)]
+        private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
+
+        [DllImport("kernel32.dll")]
+        private static extern IntPtr GetModuleHandle(string name);
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct Point
+        {
+            public readonly int X;
+            public readonly int Y;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct Msllhookstruct
+        {
+            public readonly Point Pt;
+            public readonly uint MouseData;
+            public readonly uint Flags;
+            public readonly uint Time;
+            public readonly IntPtr DwExtraInfo;
+        }
+    }
 }

+ 37 - 49
PixiEditor/Helpers/RelayCommand.cs

@@ -1,55 +1,43 @@
-using System;
-using System.Windows.Input;
-
-namespace PixiEditor.Helpers
-{
-    public class RelayCommand : ICommand
-    {
-        #region Fields
-
-        private readonly Action<object> execute;
-        private readonly Predicate<object> canExecute;
-
-        #endregion // Fields
-
-        #region Constructors
-
-        public RelayCommand(Action<object> execute)
-            : this(execute, null)
-        {
-        }
-
-        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
-        {
+using System;
+using System.Windows.Input;
+
+namespace PixiEditor.Helpers
+{
+    public class RelayCommand : ICommand
+    {
+        private readonly Action<object> execute;
+        private readonly Predicate<object> canExecute;
+
+        public RelayCommand(Action<object> execute)
+            : this(execute, null)
+        {
+        }
+
+        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
+        {
             if (execute == null)
             {
                 throw new ArgumentNullException("execute");
             }
 
-            this.execute = execute;
-            this.canExecute = canExecute;
-        }
-
-        #endregion // Constructors
-
-        #region ICommand Members
-
-        public bool CanExecute(object parameter)
-        {
-            return canExecute == null ? true : canExecute(parameter);
-        }
-
-        public event EventHandler CanExecuteChanged
-        {
-            add => CommandManager.RequerySuggested += value;
-            remove => CommandManager.RequerySuggested -= value;
-        }
-
-        public void Execute(object parameter)
-        {
-            execute(parameter);
-        }
-
-        #endregion // ICommand Members
-    }
+            this.execute = execute;
+            this.canExecute = canExecute;
+        }
+
+        public event EventHandler CanExecuteChanged
+        {
+            add => CommandManager.RequerySuggested += value;
+            remove => CommandManager.RequerySuggested -= value;
+        }
+
+        public bool CanExecute(object parameter)
+        {
+            return canExecute == null ? true : canExecute(parameter);
+        }
+
+        public void Execute(object parameter)
+        {
+            execute(parameter);
+        }
+    }
 }

+ 110 - 113
PixiEditor/Models/Colors/ExColor.cs

@@ -1,77 +1,77 @@
-using System;
-using System.Windows.Media;
-
-namespace PixiEditor.Models.Colors
-{
-    public static class ExColor
-    {
-        /// <summary>
-        ///     Creates color with corrected brightness.
-        /// </summary>
-        /// <param name="color">Color to correct.</param>
-        /// <param name="correctionFactor">
-        ///     The brightness correction factor. Must be between -1 and 1.
-        ///     Negative values produce darker colors.
-        /// </param>
-        /// <returns>
-        ///     Corrected <see cref="Color" /> structure.
-        /// </returns>
-        public static Color ChangeColorBrightness(Color color, float correctionFactor)
-        {
-            Tuple<int, float, float> hsl = RgbToHsl(color.R, color.G, color.B);
-            int h = hsl.Item1;
-            float s = hsl.Item2;
-            float l = hsl.Item3;
-
-            l = Math.Clamp(l + correctionFactor, 0, 100);
-            Color rgb = HslToRgb(h, s, l);
-
-            return Color.FromArgb(color.A, rgb.R, rgb.G, rgb.B);
-        }
-
-        /// <summary>
-        ///     Converts RGB to HSL
-        /// </summary>
-        /// <param name="r">Red value</param>
-        /// <param name="b">Blue value</param>
-        /// <param name="g">Green value</param>
-        /// <returns>Tuple with 3 values in order: h, s, l0</returns>
-        public static Tuple<int, float, float> RgbToHsl(int r, int g, int b)
-        {
-            int h;
-            float s, l;
-            float dR = r / 255.0f;
-            float dG = g / 255.0f;
-            float dB = b / 255.0f;
-
-            float min = Math.Min(Math.Min(dR, dG), dB);
-            float max = Math.Max(Math.Max(dR, dG), dB);
-            float delta = max - min;
-
-            l = (max + min) / 2;
-
-            if (delta == 0)
-            {
-                h = 0;
-                s = 0.0f;
-            }
-            else
-            {
-                s = l <= 0.5 ? delta / (max + min) : delta / (2 - max - min);
-
-                float hue;
-
+using System;
+using System.Windows.Media;
+
+namespace PixiEditor.Models.Colors
+{
+    public static class ExColor
+    {
+        /// <summary>
+        ///     Creates color with corrected brightness.
+        /// </summary>
+        /// <param name="color">Color to correct.</param>
+        /// <param name="correctionFactor">
+        ///     The brightness correction factor. Must be between -1 and 1.
+        ///     Negative values produce darker colors.
+        /// </param>
+        /// <returns>
+        ///     Corrected <see cref="Color" /> structure.
+        /// </returns>
+        public static Color ChangeColorBrightness(Color color, float correctionFactor)
+        {
+            Tuple<int, float, float> hsl = RgbToHsl(color.R, color.G, color.B);
+            int h = hsl.Item1;
+            float s = hsl.Item2;
+            float l = hsl.Item3;
+
+            l = Math.Clamp(l + correctionFactor, 0, 100);
+            Color rgb = HslToRgb(h, s, l);
+
+            return Color.FromArgb(color.A, rgb.R, rgb.G, rgb.B);
+        }
+
+        /// <summary>
+        ///     Converts RGB to HSL.
+        /// </summary>
+        /// <param name="r">Red value.</param>
+        /// <param name="g">Green value.</param>
+        /// <param name="b">Blue value.</param>
+        /// <returns>Tuple with 3 values in order: h, s, l0.</returns>
+        public static Tuple<int, float, float> RgbToHsl(int r, int g, int b)
+        {
+            int h;
+            float s, l;
+            float dR = r / 255.0f;
+            float dG = g / 255.0f;
+            float dB = b / 255.0f;
+
+            float min = Math.Min(Math.Min(dR, dG), dB);
+            float max = Math.Max(Math.Max(dR, dG), dB);
+            float delta = max - min;
+
+            l = (max + min) / 2;
+
+            if (delta == 0)
+            {
+                h = 0;
+                s = 0.0f;
+            }
+            else
+            {
+                s = l <= 0.5 ? delta / (max + min) : delta / (2 - max - min);
+
+                float hue;
+
                 if (dR == max)
                 {
                     hue = (dG - dB) / 6 / delta;
                 }
                 else if (dG == max)
                 {
-                    hue = 1.0f / 3 + (dB - dR) / 6 / delta;
+                    hue = (1.0f / 3) + ((dB - dR) / 6 / delta);
                 }
                 else
                 {
-                    hue = 2.0f / 3 + (dR - dG) / 6 / delta;
+                    hue = (2.0f / 3) + ((dR - dG) / 6 / delta);
                 }
 
                 if (hue < 0)
@@ -84,49 +84,46 @@ namespace PixiEditor.Models.Colors
                     hue -= 1;
                 }
 
-                h = (int)(hue * 360);
-            }
-
-            return new Tuple<int, float, float>(h, s * 100, l * 100);
-        }
-
-        /// <summary>
-        ///     Converts HSL color format to RGB
-        /// </summary>
-        /// <param name="h"></param>
-        /// <param name="s"></param>
-        /// <param name="l"></param>
-        /// <returns>RGB Color</returns>
-        public static Color HslToRgb(int h, float s, float l)
-        {
-            s /= 100;
-            l /= 100;
-            byte r = 0;
-            byte g = 0;
-            byte b = 0;
-
-            if (s == 0)
-            {
-                r = g = b = (byte)(l * 255);
-            }
-            else
-            {
-                float v1, v2;
-                float hue = (float)h / 360;
-
-                v2 = l < 0.5 ? l * (1 + s) : l + s - l * s;
-                v1 = 2 * l - v2;
-
-                r = (byte)(255 * HueToRgb(v1, v2, hue + 1.0f / 3));
-                g = (byte)(255 * HueToRgb(v1, v2, hue));
-                b = (byte)(255 * HueToRgb(v1, v2, hue - 1.0f / 3));
-            }
-
-            return Color.FromRgb(r, g, b);
-        }
-
-        private static float HueToRgb(float v1, float v2, float hue)
-        {
+                h = (int)(hue * 360);
+            }
+
+            return new Tuple<int, float, float>(h, s * 100, l * 100);
+        }
+
+        /// <summary>
+        ///     Converts HSL color format to RGB.
+        /// </summary>
+        /// <returns>RGB Color.</returns>
+        public static Color HslToRgb(int h, float s, float l)
+        {
+            s /= 100;
+            l /= 100;
+            byte r = 0;
+            byte g = 0;
+            byte b = 0;
+
+            if (s == 0)
+            {
+                r = g = b = (byte)(l * 255);
+            }
+            else
+            {
+                float v1, v2;
+                float hue = (float)h / 360;
+
+                v2 = l < 0.5 ? l * (1 + s) : l + s - (l * s);
+                v1 = (2 * l) - v2;
+
+                r = (byte)(255 * HueToRgb(v1, v2, hue + (1.0f / 3)));
+                g = (byte)(255 * HueToRgb(v1, v2, hue));
+                b = (byte)(255 * HueToRgb(v1, v2, hue - (1.0f / 3)));
+            }
+
+            return Color.FromRgb(r, g, b);
+        }
+
+        private static float HueToRgb(float v1, float v2, float hue)
+        {
             if (hue < 0)
             {
                 hue += 1;
@@ -139,7 +136,7 @@ namespace PixiEditor.Models.Colors
 
             if (6 * hue < 1)
             {
-                return v1 + (v2 - v1) * 6 * hue;
+                return v1 + ((v2 - v1) * 6 * hue);
             }
 
             if (2 * hue < 1)
@@ -149,10 +146,10 @@ namespace PixiEditor.Models.Colors
 
             if (3 * hue < 2)
             {
-                return v1 + (v2 - v1) * (2.0f / 3 - hue) * 6;
+                return v1 + ((v2 - v1) * ((2.0f / 3) - hue) * 6);
             }
 
-            return v1;
-        }
-    }
+            return v1;
+        }
+    }
 }

+ 18 - 0
PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs

@@ -0,0 +1,18 @@
+using System;
+using PixiEditor.Models.DataHolders;
+
+public class BitmapChangedEventArgs : EventArgs
+{
+    public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, BitmapPixelChanges oldPixelsValues, int changedLayerIndex)
+    {
+        PixelsChanged = pixelsChanged;
+        OldPixelsValues = oldPixelsValues;
+        ChangedLayerIndex = changedLayerIndex;
+    }
+
+    public BitmapPixelChanges PixelsChanged { get; set; }
+
+    public BitmapPixelChanges OldPixelsValues { get; set; }
+
+    public int ChangedLayerIndex { get; set; }
+}

+ 243 - 256
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -1,153 +1,161 @@
-using System;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Helpers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Events;
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-
-namespace PixiEditor.Models.Controllers
-{
-    public class BitmapManager : NotifyableObject
-    {
-        private Document activeDocument;
-
-        private Layer previewLayer;
-        private Tool selectedTool;
-
-        public BitmapManager()
-        {
-            MouseController = new MouseMovementController();
-            MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
-            MouseController.MousePositionChanged += Controller_MousePositionChanged;
-            MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
-            BitmapOperations = new BitmapOperationsUtility(this);
-            ReadonlyToolUtility = new ReadonlyToolUtility();
-        }
-
-        public MouseMovementController MouseController { get; set; }
-
-        public Tool SelectedTool
-        {
-            get => selectedTool;
-            private set
-            {
-                selectedTool = value;
-                RaisePropertyChanged("SelectedTool");
-            }
-        }
-
-        public Layer PreviewLayer
-        {
-            get => previewLayer;
-            set
-            {
-                previewLayer = value;
-                RaisePropertyChanged("PreviewLayer");
-            }
-        }
-
-        public Layer ActiveLayer => ActiveDocument.ActiveLayer;
-
-        public Color PrimaryColor { get; set; }
-
-        public int ToolSize
-        {
-            get => SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") != null
-                ? SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize").Value
-                : 1;
-            set
-            {
-                if (SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is var toolSize)
-                {
-                    toolSize.Value = value;
-                    HighlightPixels(MousePositionConverter.CurrentCoordinates);
-                }
-            }
-        }
-
-        public BitmapOperationsUtility BitmapOperations { get; set; }
-
-        public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
-
-        public Document ActiveDocument
-        {
-            get => activeDocument;
-            set
-            {
-                activeDocument = value;
-                RaisePropertyChanged("ActiveDocument");
-                DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value));
-            }
-        }
-
-        public event EventHandler<LayersChangedEventArgs> LayersChanged;
-
-        public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
-
-        public void SetActiveTool(Tool tool)
-        {
-            PreviewLayer = null;
-            SelectedTool?.Toolbar.SaveToolbarSettings();
-            SelectedTool = tool;
-            SelectedTool.Toolbar.LoadSharedSettings();
-        }
-
-        public void SetActiveLayer(int index)
-        {
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Events;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class BitmapManager : NotifyableObject
+    {
+        private Document activeDocument;
+
+        private Layer previewLayer;
+        private Tool selectedTool;
+
+        public BitmapManager()
+        {
+            MouseController = new MouseMovementController();
+            MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
+            MouseController.MousePositionChanged += Controller_MousePositionChanged;
+            MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
+            BitmapOperations = new BitmapOperationsUtility(this);
+            ReadonlyToolUtility = new ReadonlyToolUtility();
+        }
+
+        public event EventHandler<LayersChangedEventArgs> LayersChanged;
+
+        public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
+
+        public MouseMovementController MouseController { get; set; }
+
+        public Tool SelectedTool
+        {
+            get => selectedTool;
+            private set
+            {
+                selectedTool = value;
+                RaisePropertyChanged("SelectedTool");
+            }
+        }
+
+        public Layer PreviewLayer
+        {
+            get => previewLayer;
+            set
+            {
+                previewLayer = value;
+                RaisePropertyChanged("PreviewLayer");
+            }
+        }
+
+        public Layer ActiveLayer => ActiveDocument.ActiveLayer;
+
+        public Color PrimaryColor { get; set; }
+
+        public int ToolSize
+        {
+            get => SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") != null
+                ? SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize").Value
+                : 1;
+            set
+            {
+                if (SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is var toolSize)
+                {
+                    toolSize.Value = value;
+                    HighlightPixels(MousePositionConverter.CurrentCoordinates);
+                }
+            }
+        }
+
+        public BitmapOperationsUtility BitmapOperations { get; set; }
+
+        public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
+
+        public Document ActiveDocument
+        {
+            get => activeDocument;
+            set
+            {
+                activeDocument = value;
+                RaisePropertyChanged("ActiveDocument");
+                DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value));
+            }
+        }
+
+        /// <summary>
+        ///     Returns if tool is BitmapOperationTool.
+        /// </summary>
+        public static bool IsOperationTool(Tool tool)
+        {
+            return tool is BitmapOperationTool;
+        }
+
+        public void SetActiveTool(Tool tool)
+        {
+            PreviewLayer = null;
+            SelectedTool?.Toolbar.SaveToolbarSettings();
+            SelectedTool = tool;
+            SelectedTool.Toolbar.LoadSharedSettings();
+        }
+
+        public void SetActiveLayer(int index)
+        {
             if (ActiveDocument.ActiveLayerIndex <= ActiveDocument.Layers.Count - 1)
             {
                 ActiveDocument.ActiveLayer.IsActive = false;
             }
 
-            ActiveDocument.ActiveLayerIndex = index;
-            ActiveDocument.ActiveLayer.IsActive = true;
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
-        }
-
-        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
-        {
-            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
-            ActiveDocument.Layers.Last().LayerBitmap = bitmap;
-        }
-
-        public void AddNewLayer(string name, bool setAsActive = true)
-        {
-            AddNewLayer(name, 0, 0, setAsActive);
-        }
-
-        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
-        {
-            ActiveDocument.Layers.Add(new Layer(name, width, height)
-            {
-                MaxHeight = ActiveDocument.Height,
-                MaxWidth = ActiveDocument.Width
-            });
+            ActiveDocument.ActiveLayerIndex = index;
+            ActiveDocument.ActiveLayer.IsActive = true;
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
+        }
+
+        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
+        {
+            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
+            ActiveDocument.Layers.Last().LayerBitmap = bitmap;
+        }
+
+        public void AddNewLayer(string name, bool setAsActive = true)
+        {
+            AddNewLayer(name, 0, 0, setAsActive);
+        }
+
+        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
+        {
+            ActiveDocument.Layers.Add(new Layer(name, width, height)
+            {
+                MaxHeight = ActiveDocument.Height,
+                MaxWidth = ActiveDocument.Width
+            });
             if (setAsActive)
             {
                 SetActiveLayer(ActiveDocument.Layers.Count - 1);
             }
 
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
-        }
-
-        public void RemoveLayer(int layerIndex)
-        {
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
+        }
+
+        public void RemoveLayer(int layerIndex)
+        {
             if (ActiveDocument.Layers.Count == 0)
             {
                 return;
             }
 
-            bool wasActive = ActiveDocument.Layers[layerIndex].IsActive;
-            ActiveDocument.Layers.RemoveAt(layerIndex);
+            bool wasActive = ActiveDocument.Layers[layerIndex].IsActive;
+            ActiveDocument.Layers.RemoveAt(layerIndex);
             if (wasActive)
             {
                 SetActiveLayer(0);
@@ -156,143 +164,122 @@ namespace PixiEditor.Models.Controllers
             {
                 SetActiveLayer(ActiveDocument.Layers.Count - 1);
             }
-        }
-
-        private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
-        {
-            SelectedTool.OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (Mouse.LeftButton == MouseButtonState.Pressed && !IsDraggingViewport() && ActiveDocument != null)
-            {
-                ExecuteTool(e.NewPosition, MouseController.ClickedOnCanvas);
-            }
-            else if (Mouse.LeftButton == MouseButtonState.Released)
+        }
+
+        public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
+        {
+            if (SelectedTool.CanStartOutsideCanvas || clickedOnCanvas)
             {
-                HighlightPixels(e.NewPosition);
-            }
-        }
-
-        public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
-        {
-            if (SelectedTool.CanStartOutsideCanvas || clickedOnCanvas)
-            {
                 if (IsOperationTool(SelectedTool))
                 {
-                    BitmapOperations.ExecuteTool(newPosition,
-                        MouseController.LastMouseMoveCoordinates.ToList(), (BitmapOperationTool)SelectedTool);
+                    BitmapOperations.ExecuteTool(
+                        newPosition,
+                        MouseController.LastMouseMoveCoordinates.ToList(),
+                        (BitmapOperationTool)SelectedTool);
                 }
                 else
                 {
-                    ReadonlyToolUtility.ExecuteTool(MouseController.LastMouseMoveCoordinates.ToArray(),
+                    ReadonlyToolUtility.ExecuteTool(
+                        MouseController.LastMouseMoveCoordinates.ToArray(),
                         (ReadonlyTool)SelectedTool);
                 }
-            }
-        }
-
-        private bool IsDraggingViewport()
-        {
-            return Keyboard.IsKeyDown(Key.LeftShift) && !(SelectedTool is ShapeTool);
-        }
-
-        private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
-        {
-            SelectedTool.OnMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            PreviewLayer = null;
-        }
-
-        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
-        {
-            SelectedTool.OnMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (IsOperationTool(SelectedTool) && ((BitmapOperationTool)SelectedTool).RequiresPreviewLayer)
-            {
-                BitmapOperations.StopAction();
             }
-        }
-
-        public void GeneratePreviewLayer()
-        {
+        }
+
+        public void GeneratePreviewLayer()
+        {
             if (ActiveDocument != null)
             {
-                PreviewLayer = new Layer("_previewLayer")
-                {
-                    MaxWidth = ActiveDocument.Width,
-                    MaxHeight = ActiveDocument.Height
+                PreviewLayer = new Layer("_previewLayer")
+                {
+                    MaxWidth = ActiveDocument.Width,
+                    MaxHeight = ActiveDocument.Height
                 };
             }
-        }
-
-        private void HighlightPixels(Coordinates newPosition)
-        {
+        }
+
+        public WriteableBitmap GetCombinedLayersBitmap()
+        {
+            return BitmapUtils.CombineLayers(ActiveDocument.Layers.Where(x => x.IsVisible).ToArray(), ActiveDocument.Width, ActiveDocument.Height);
+        }
+
+        /// <summary>
+        ///     Returns if selected tool is BitmapOperationTool.
+        /// </summary>
+        public bool IsOperationTool()
+        {
+            return IsOperationTool(SelectedTool);
+        }
+
+        private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
+        {
+            SelectedTool.OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            if (Mouse.LeftButton == MouseButtonState.Pressed && !IsDraggingViewport() && ActiveDocument != null)
+            {
+                ExecuteTool(e.NewPosition, MouseController.ClickedOnCanvas);
+            }
+            else if (Mouse.LeftButton == MouseButtonState.Released)
+            {
+                HighlightPixels(e.NewPosition);
+            }
+        }
+
+        private bool IsDraggingViewport()
+        {
+            return Keyboard.IsKeyDown(Key.LeftShift) && !(SelectedTool is ShapeTool);
+        }
+
+        private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
+        {
+            SelectedTool.OnMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            PreviewLayer = null;
+        }
+
+        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
+        {
+            SelectedTool.OnMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            if (IsOperationTool(SelectedTool) && ((BitmapOperationTool)SelectedTool).RequiresPreviewLayer)
+            {
+                BitmapOperations.StopAction();
+            }
+        }
+
+        private void HighlightPixels(Coordinates newPosition)
+        {
             if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || SelectedTool.HideHighlight)
             {
                 return;
             }
 
-            Coordinates[] highlightArea = CoordinatesCalculator.RectangleToCoordinates(
-                CoordinatesCalculator.CalculateThicknessCenter(newPosition, ToolSize));
-            if (CanChangeHighlightOffset(highlightArea))
-            {
-                PreviewLayer.Offset = new Thickness(highlightArea[0].X, highlightArea[0].Y, 0, 0);
-            }
-            else if (!IsInsideBounds(highlightArea))
-            {
-                PreviewLayer = null;
-            }
-            else
-            {
-                GeneratePreviewLayer();
-                PreviewLayer.SetPixels(
-                    BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77, 0, 0, 0)));
-            }
-        }
-
-        private bool CanChangeHighlightOffset(Coordinates[] highlightArea)
-        {
-            return highlightArea.Length > 0 && PreviewLayer != null &&
-                   IsInsideBounds(highlightArea) && highlightArea.Length == PreviewLayer.Width * PreviewLayer.Height;
-        }
-
-        private bool IsInsideBounds(Coordinates[] highlightArea)
-        {
-            return highlightArea[0].X <= ActiveDocument.Width - 1 &&
-                   highlightArea[0].Y <= ActiveDocument.Height - 1 &&
-                   highlightArea[^1].X >= 0 && highlightArea[^1].Y >= 0;
-        }
-
-        public WriteableBitmap GetCombinedLayersBitmap()
-        {
-            return BitmapUtils.CombineLayers(ActiveDocument.Layers.Where(x => x.IsVisible).ToArray(), ActiveDocument.Width, ActiveDocument.Height);
-        }
-
-        /// <summary>
-        ///     Returns if selected tool is BitmapOperationTool
-        /// </summary>
-        /// <returns></returns>
-        public bool IsOperationTool()
-        {
-            return IsOperationTool(SelectedTool);
-        }
-
-        /// <summary>
-        ///     Returns if tool is BitmapOperationTool
-        /// </summary>
-        /// <param name="tool"></param>
-        /// <returns></returns>
-        public static bool IsOperationTool(Tool tool)
-        {
-            return tool is BitmapOperationTool;
-        }
-    }
-
-    public class LayersChangedEventArgs : EventArgs
-    {
-        public LayersChangedEventArgs(int layerAffected, LayerAction layerChangeType)
-        {
-            LayerAffected = layerAffected;
-            LayerChangeType = layerChangeType;
-        }
-
-        public int LayerAffected { get; set; }
-
-        public LayerAction LayerChangeType { get; set; }
-    }
+            Coordinates[] highlightArea = CoordinatesCalculator.RectangleToCoordinates(
+                CoordinatesCalculator.CalculateThicknessCenter(newPosition, ToolSize));
+            if (CanChangeHighlightOffset(highlightArea))
+            {
+                PreviewLayer.Offset = new Thickness(highlightArea[0].X, highlightArea[0].Y, 0, 0);
+            }
+            else if (!IsInsideBounds(highlightArea))
+            {
+                PreviewLayer = null;
+            }
+            else
+            {
+                GeneratePreviewLayer();
+                PreviewLayer.SetPixels(
+                    BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77, 0, 0, 0)));
+            }
+        }
+
+        private bool CanChangeHighlightOffset(Coordinates[] highlightArea)
+        {
+            return highlightArea.Length > 0 && PreviewLayer != null &&
+                   IsInsideBounds(highlightArea) && highlightArea.Length == PreviewLayer.Width * PreviewLayer.Height;
+        }
+
+        private bool IsInsideBounds(Coordinates[] highlightArea)
+        {
+            return highlightArea[0].X <= ActiveDocument.Width - 1 &&
+                   highlightArea[0].Y <= ActiveDocument.Height - 1 &&
+                   highlightArea[^1].X >= 0 && highlightArea[^1].Y >= 0;
+        }
+    }
 }

+ 21 - 33
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -23,10 +23,10 @@ namespace PixiEditor.Models.Controllers
             Manager = manager;
         }
 
-        public BitmapManager Manager { get; set; }
-
         public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
 
+        public BitmapManager Manager { get; set; }
+
         public void DeletePixels(Layer[] layers, Coordinates[] pixels)
         {
             BitmapPixelChanges changes = BitmapPixelChanges.FromSingleColoredArray(pixels, Color.FromArgb(0, 0, 0, 0));
@@ -45,11 +45,11 @@ namespace PixiEditor.Models.Controllers
         }
 
         /// <summary>
-        ///     Executes tool Use() method with given parameters. NOTE: mouseMove is reversed inside function!
+        ///     Executes tool Use() method with given parameters. NOTE: mouseMove is reversed inside function!.
         /// </summary>
-        /// <param name="newPos">Most recent coordinates</param>
-        /// <param name="mouseMove">Last mouse movement coordinates</param>
-        /// <param name="tool">Tool to execute</param>
+        /// <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)
         {
             if (Manager.ActiveDocument != null && tool != null && tool.ToolType != ToolType.None)
@@ -67,7 +67,7 @@ namespace PixiEditor.Models.Controllers
         }
 
         /// <summary>
-        ///     Applies pixels from preview layer to selected layer
+        ///     Applies pixels from preview layer to selected layer.
         /// </summary>
         public void StopAction()
         {
@@ -82,8 +82,10 @@ namespace PixiEditor.Models.Controllers
 
                 BitmapPixelChanges oldValues = ApplyToLayer(layer, lastModifiedLayers[i]).PixelChanges;
 
-                BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(lastModifiedLayers[i].PixelChanges,
-                    oldValues, lastModifiedLayers[i].LayerIndex));
+                BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(
+                    lastModifiedLayers[i].PixelChanges,
+                    oldValues,
+                    lastModifiedLayers[i].LayerIndex));
                 Manager.PreviewLayer = null;
             }
         }
@@ -104,8 +106,10 @@ namespace PixiEditor.Models.Controllers
                     Layer layer = Manager.ActiveDocument.Layers[modifiedLayers[i].LayerIndex];
                     oldPixelsValues[i] = ApplyToLayer(layer, modifiedLayers[i]);
 
-                    BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(modifiedLayers[i].PixelChanges,
-                        oldPixelsValues[i].PixelChanges, modifiedLayers[i].LayerIndex));
+                    BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(
+                        modifiedLayers[i].PixelChanges,
+                        oldPixelsValues[i].PixelChanges,
+                        modifiedLayers[i].LayerIndex));
                 }
             }
             else
@@ -134,8 +138,6 @@ namespace PixiEditor.Models.Controllers
         /// <summary>
         ///     Extracts square from rectangle mouse drag, used to draw symmetric shapes.
         /// </summary>
-        /// <param name="mouseMoveCords"></param>
-        /// <returns></returns>
         private List<Coordinates> GetSquareCoordiantes(List<Coordinates> mouseMoveCords)
         {
             int xLength = mouseMoveCords[0].Y - mouseMoveCords[^1].Y;
@@ -162,7 +164,8 @@ namespace PixiEditor.Models.Controllers
                 Coordinates[] relativeCoords = Manager.ActiveLayer.ConvertToRelativeCoordinates(coordinates);
                 for (int i = 0; i < coordinates.Length; i++)
                 {
-                    values.Add(coordinates[i],
+                    values.Add(
+                        coordinates[i],
                         Manager.ActiveLayer.GetPixel(relativeCoords[i].X, relativeCoords[i].Y));
                 }
             }
@@ -176,29 +179,14 @@ namespace PixiEditor.Models.Controllers
             if (mouseMove.Count > 0 && mouseMove[0] != lastMousePos)
             {
                 Manager.GeneratePreviewLayer();
-                modifiedLayers = ((BitmapOperationTool)Manager.SelectedTool).Use(Manager.ActiveDocument.ActiveLayer,
-                    mouseMove.ToArray(), Manager.PrimaryColor);
+                modifiedLayers = ((BitmapOperationTool)Manager.SelectedTool).Use(
+                    Manager.ActiveDocument.ActiveLayer,
+                    mouseMove.ToArray(),
+                    Manager.PrimaryColor);
                 BitmapPixelChanges[] changes = modifiedLayers.Select(x => x.PixelChanges).ToArray();
                 Manager.PreviewLayer.SetPixels(BitmapPixelChanges.CombineOverride(changes));
                 lastModifiedLayers = modifiedLayers;
             }
         }
     }
-}
-
-public class BitmapChangedEventArgs : EventArgs
-{
-    public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, BitmapPixelChanges oldPixelsValues,
-        int changedLayerIndex)
-    {
-        PixelsChanged = pixelsChanged;
-        OldPixelsValues = oldPixelsValues;
-        ChangedLayerIndex = changedLayerIndex;
-    }
-
-    public BitmapPixelChanges PixelsChanged { get; set; }
-
-    public BitmapPixelChanges OldPixelsValues { get; set; }
-
-    public int ChangedLayerIndex { get; set; }
 }

+ 86 - 90
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -1,111 +1,107 @@
-using System.IO;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Models.Controllers
-{
-    public static class ClipboardController
-    {
-        /// <summary>
-        ///     Copies selection to clipboard in PNG, Bitmap and DIB formats.
-        /// </summary>
-        /// <param name="layers">Layers where selection is</param>
-        /// <param name="selection"></param>
-        /// <param name="originalImageWidth">Output </param>
-        /// <param name="originalImageHeight"></param>
-        public static void CopyToClipboard(Layer[] layers, Coordinates[] selection, int originalImageWidth, int originalImageHeight)
-        {
-            Clipboard.Clear();
-            WriteableBitmap combinedBitmaps = BitmapUtils.CombineLayers(layers, originalImageWidth, originalImageHeight);
-            using (MemoryStream pngStream = new MemoryStream())
-            {
-                DataObject data = new DataObject();
-                BitmapSource croppedBmp = BitmapSelectionToBmpSource(combinedBitmaps, selection);
-                data.SetData(DataFormats.Bitmap, croppedBmp, true); // Bitmap, no transparency support
-
-                PngBitmapEncoder encoder = new PngBitmapEncoder();
-                encoder.Frames.Add(BitmapFrame.Create(croppedBmp));
-                encoder.Save(pngStream);
-                data.SetData("PNG", pngStream, false); // PNG, supports transparency
-
-                Clipboard.SetImage(croppedBmp); // DIB format
-                Clipboard.SetDataObject(data, true);
-            }
-        }
-
-        /// <summary>
-        ///     Pastes image from clipboard into new layer.
-        /// </summary>
-        public static void PasteFromClipboard()
-        {
-            WriteableBitmap image = GetImageFromClipboard();
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.Controllers
+{
+    public static class ClipboardController
+    {
+        /// <summary>
+        ///     Copies selection to clipboard in PNG, Bitmap and DIB formats.
+        /// </summary>
+        /// <param name="layers">Layers where selection is.</param>
+        public static void CopyToClipboard(Layer[] layers, Coordinates[] selection, int originalImageWidth, int originalImageHeight)
+        {
+            Clipboard.Clear();
+            WriteableBitmap combinedBitmaps = BitmapUtils.CombineLayers(layers, originalImageWidth, originalImageHeight);
+            using (MemoryStream pngStream = new MemoryStream())
+            {
+                DataObject data = new DataObject();
+                BitmapSource croppedBmp = BitmapSelectionToBmpSource(combinedBitmaps, selection);
+                data.SetData(DataFormats.Bitmap, croppedBmp, true); // Bitmap, no transparency support
+
+                PngBitmapEncoder encoder = new PngBitmapEncoder();
+                encoder.Frames.Add(BitmapFrame.Create(croppedBmp));
+                encoder.Save(pngStream);
+                data.SetData("PNG", pngStream, false); // PNG, supports transparency
+
+                Clipboard.SetImage(croppedBmp); // DIB format
+                Clipboard.SetDataObject(data, true);
+            }
+        }
+
+        /// <summary>
+        ///     Pastes image from clipboard into new layer.
+        /// </summary>
+        public static void PasteFromClipboard()
+        {
+            WriteableBitmap image = GetImageFromClipboard();
             if (image != null)
             {
                 AddImageToLayers(image);
             }
-        }
-
-        /// <summary>
-        ///     Gets image from clipboard, supported PNG, Dib and Bitmap
-        /// </summary>
-        /// <returns>WriteableBitmap</returns>
-        public static WriteableBitmap GetImageFromClipboard()
-        {
-            DataObject dao = (DataObject)Clipboard.GetDataObject();
-            WriteableBitmap finalImage = null;
+        }
+
+        /// <summary>
+        ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
+        /// </summary>
+        /// <returns>WriteableBitmap.</returns>
+        public static WriteableBitmap GetImageFromClipboard()
+        {
+            DataObject dao = (DataObject)Clipboard.GetDataObject();
+            WriteableBitmap finalImage = null;
             if (dao.GetDataPresent("PNG"))
             {
-                using (MemoryStream pngStream = dao.GetData("PNG") as MemoryStream)
-                {
-                    if (pngStream != null)
-                    {
-                        PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache,
-                            BitmapCacheOption.OnLoad);
-                        finalImage = new WriteableBitmap(decoder.Frames[0].Clone());
-                    }
+                using (MemoryStream pngStream = dao.GetData("PNG") as MemoryStream)
+                {
+                    if (pngStream != null)
+                    {
+                        PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
+                        finalImage = new WriteableBitmap(decoder.Frames[0].Clone());
+                    }
                 }
             }
             else if (dao.GetDataPresent(DataFormats.Dib))
             {
-                finalImage = new WriteableBitmap(Clipboard.GetImage()!);
+                finalImage = new WriteableBitmap(Clipboard.GetImage() !);
             }
             else if (dao.GetDataPresent(DataFormats.Bitmap))
             {
-                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource)!);
+                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource) !);
             }
 
-            return finalImage;
-        }
-
-        public static bool IsImageInClipboard()
-        {
-            DataObject dao = (DataObject)Clipboard.GetDataObject();
+            return finalImage;
+        }
+
+        public static bool IsImageInClipboard()
+        {
+            DataObject dao = (DataObject)Clipboard.GetDataObject();
             if (dao == null)
             {
                 return false;
             }
 
-            return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
-                   dao.GetDataPresent(DataFormats.Bitmap);
-        }
-
-        private static void AddImageToLayers(WriteableBitmap image)
-        {
-            ViewModelMain.Current.BitmapManager.AddNewLayer("Image", image);
-        }
-
-        public static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection)
-        {
-            int offsetX = selection.Min(x => x.X);
-            int offsetY = selection.Min(x => x.Y);
-            int width = selection.Max(x => x.X) - offsetX + 1;
-            int height = selection.Max(x => x.Y) - offsetY + 1;
-            return bitmap.Crop(offsetX, offsetY, width, height);
-        }
-    }
+            return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
+                   dao.GetDataPresent(DataFormats.Bitmap);
+        }
+
+        public static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection)
+        {
+            int offsetX = selection.Min(x => x.X);
+            int offsetY = selection.Min(x => x.Y);
+            int width = selection.Max(x => x.X) - offsetX + 1;
+            int height = selection.Max(x => x.Y) - offsetY + 1;
+            return bitmap.Crop(offsetX, offsetY, width, height);
+        }
+
+        private static void AddImageToLayers(WriteableBitmap image)
+        {
+            ViewModelMain.Current.BitmapManager.AddNewLayer("Image", image);
+        }
+    }
 }

+ 18 - 0
PixiEditor/Models/Controllers/LayersChangedEventArgs.cs

@@ -0,0 +1,18 @@
+using System;
+using PixiEditor.Models.Enums;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class LayersChangedEventArgs : EventArgs
+    {
+        public LayersChangedEventArgs(int layerAffected, LayerAction layerChangeType)
+        {
+            LayerAffected = layerAffected;
+            LayerChangeType = layerChangeType;
+        }
+
+        public int LayerAffected { get; set; }
+
+        public LayerAction LayerChangeType { get; set; }
+    }
+}

+ 7 - 18
PixiEditor/Models/Controllers/MouseMovementController.cs

@@ -6,18 +6,18 @@ namespace PixiEditor.Models.Controllers
 {
     public class MouseMovementController
     {
-        public List<Coordinates> LastMouseMoveCoordinates { get; } = new List<Coordinates>();
-
-        public bool IsRecordingChanges { get; private set; }
-
-        public bool ClickedOnCanvas { get; set; }
-
         public event EventHandler StartedRecordingChanges;
 
         public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
 
         public event EventHandler StoppedRecordingChanges;
 
+        public List<Coordinates> LastMouseMoveCoordinates { get; } = new List<Coordinates>();
+
+        public bool IsRecordingChanges { get; private set; }
+
+        public bool ClickedOnCanvas { get; set; }
+
         public void StartRecordingMouseMovementChanges(bool clickedOnCanvas)
         {
             if (IsRecordingChanges == false)
@@ -42,9 +42,8 @@ namespace PixiEditor.Models.Controllers
         }
 
         /// <summary>
-        ///     Plain mouse move, does not affect mouse drag recordings
+        ///     Plain mouse move, does not affect mouse drag recordings.
         /// </summary>
-        /// <param name="mouseCoordinates"></param>
         public void MouseMoved(Coordinates mouseCoordinates)
         {
             MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
@@ -60,14 +59,4 @@ namespace PixiEditor.Models.Controllers
             }
         }
     }
-}
-
-public class MouseMovementEventArgs : EventArgs
-{
-    public MouseMovementEventArgs(Coordinates mousePosition)
-    {
-        NewPosition = mousePosition;
-    }
-
-    public Coordinates NewPosition { get; set; }
 }

+ 12 - 0
PixiEditor/Models/Controllers/MouseMovementEventArgs.cs

@@ -0,0 +1,12 @@
+using System;
+using PixiEditor.Models.Position;
+
+public class MouseMovementEventArgs : EventArgs
+{
+    public MouseMovementEventArgs(Coordinates mousePosition)
+    {
+        NewPosition = mousePosition;
+    }
+
+    public Coordinates NewPosition { get; set; }
+}

+ 36 - 36
PixiEditor/Models/Controllers/PixelChangesController.cs

@@ -12,10 +12,10 @@ namespace PixiEditor.Models.Controllers
         private Dictionary<int, LayerChange> LastOldValues { get; set; }
 
         /// <summary>
-        ///     Adds layer changes to controller
+        ///     Adds layer changes to controller.
         /// </summary>
-        /// <param name="changes">New changes</param>
-        /// <param name="oldValues">Old values of changes</param>
+        /// <param name="changes">New changes.</param>
+        /// <param name="oldValues">Old values of changes.</param>
         public void AddChanges(LayerChange changes, LayerChange oldValues)
         {
             if (changes.PixelChanges.ChangedPixels.Count > 0)
@@ -36,6 +36,39 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
+        /// <summary>
+        ///     Returns all changes and deletes them from controller.
+        /// </summary>
+        /// <returns>Tuple array with new changes and old values.</returns>
+        public Tuple<LayerChange, LayerChange>[] PopChanges()
+        {
+            // Maybe replace Tuple with custom data type
+            if (LastChanges == null)
+            {
+                return null;
+            }
+
+            Tuple<LayerChange, LayerChange>[] result = new Tuple<LayerChange, LayerChange>[LastChanges.Count];
+            int i = 0;
+            foreach (KeyValuePair<int, LayerChange> change in LastChanges)
+            {
+                Dictionary<Position.Coordinates, System.Windows.Media.Color> pixelChanges =
+                    change.Value.PixelChanges.ChangedPixels.ToDictionary(entry => entry.Key, entry => entry.Value);
+                Dictionary<Position.Coordinates, System.Windows.Media.Color> oldValues = LastOldValues[change.Key].PixelChanges.ChangedPixels
+                    .ToDictionary(entry => entry.Key, entry => entry.Value);
+
+                LayerChange tmp = new LayerChange(new BitmapPixelChanges(pixelChanges), change.Key);
+                LayerChange oldValuesTmp = new LayerChange(new BitmapPixelChanges(oldValues), change.Key);
+
+                result[i] = new Tuple<LayerChange, LayerChange>(tmp, oldValuesTmp);
+                i++;
+            }
+
+            LastChanges = null;
+            LastOldValues = null;
+            return result;
+        }
+
         private void AddNewLayerChange(LayerChange changes, LayerChange oldValues)
         {
             LastChanges[changes.LayerIndex] = changes;
@@ -68,38 +101,5 @@ namespace PixiEditor.Models.Controllers
                 }
             }
         }
-
-        /// <summary>
-        ///     Returns all changes and deletes them from controller.
-        /// </summary>
-        /// <returns>Tuple array with new changes and old values</returns>
-        public Tuple<LayerChange, LayerChange>[] PopChanges()
-        {
-            // Maybe replace Tuple with custom data type
-            if (LastChanges == null)
-            {
-                return null;
-            }
-
-            Tuple<LayerChange, LayerChange>[] result = new Tuple<LayerChange, LayerChange>[LastChanges.Count];
-            int i = 0;
-            foreach (KeyValuePair<int, LayerChange> change in LastChanges)
-            {
-                Dictionary<Position.Coordinates, System.Windows.Media.Color> pixelChanges =
-                    change.Value.PixelChanges.ChangedPixels.ToDictionary(entry => entry.Key, entry => entry.Value);
-                Dictionary<Position.Coordinates, System.Windows.Media.Color> oldValues = LastOldValues[change.Key].PixelChanges.ChangedPixels
-                    .ToDictionary(entry => entry.Key, entry => entry.Value);
-
-                LayerChange tmp = new LayerChange(new BitmapPixelChanges(pixelChanges), change.Key);
-                LayerChange oldValuesTmp = new LayerChange(new BitmapPixelChanges(oldValues), change.Key);
-
-                result[i] = new Tuple<LayerChange, LayerChange>(tmp, oldValuesTmp);
-                i++;
-            }
-
-            LastChanges = null;
-            LastOldValues = null;
-            return result;
-        }
     }
 }

+ 1 - 2
PixiEditor/Models/Controllers/Shortcuts/Shortcut.cs

@@ -4,8 +4,7 @@ namespace PixiEditor.Models.Controllers.Shortcuts
 {
     public class Shortcut
     {
-        public Shortcut(Key shortcutKey, ICommand command, object commandParameter = null,
-            ModifierKeys modifier = ModifierKeys.None)
+        public Shortcut(Key shortcutKey, ICommand command, object commandParameter = null, ModifierKeys modifier = ModifierKeys.None)
         {
             ShortcutKey = shortcutKey;
             Modifier = modifier;

+ 71 - 71
PixiEditor/Models/Controllers/UndoManager.cs

@@ -1,55 +1,55 @@
-using System.Collections.Generic;
-using System.Linq;
-using PixiEditor.Models.DataHolders;
-
-namespace PixiEditor.Models.Controllers
-{
-    public static class UndoManager
-    {
-        private static bool lastChangeWasUndo;
+using System.Collections.Generic;
+using System.Linq;
+using PixiEditor.Models.DataHolders;
 
-        public static Stack<Change> UndoStack { get; set; } = new Stack<Change>();
+namespace PixiEditor.Models.Controllers
+{
+    public static class UndoManager
+    {
+        private static bool lastChangeWasUndo;
 
-        public static Stack<Change> RedoStack { get; set; } = new Stack<Change>();
-
-        public static bool CanUndo => UndoStack.Count > 0;
-
-        public static bool CanRedo => RedoStack.Count > 0;
-
-        public static object MainRoot { get; set; }
-
-        /// <summary>
-        ///     Sets object(root) in which undo properties are stored.
-        /// </summary>
-        /// <param name="root">Parent object.</param>
-        public static void SetMainRoot(object root)
-        {
-            MainRoot = root;
-        }
-
-        /// <summary>
-        ///     Adds property change to UndoStack
-        /// </summary>
-        /// <param name="change"></param>
-        public static void AddUndoChange(Change change)
-        {
-            if (lastChangeWasUndo == false && RedoStack.Count > 0) // Clears RedoStack if las move wasn't redo or undo and if redo stack is greater than 0
+        public static Stack<Change> UndoStack { get; set; } = new Stack<Change>();
+
+        public static Stack<Change> RedoStack { get; set; } = new Stack<Change>();
+
+        public static bool CanUndo => UndoStack.Count > 0;
+
+        public static bool CanRedo => RedoStack.Count > 0;
+
+        public static object MainRoot { get; set; }
+
+        /// <summary>
+        ///     Sets object(root) in which undo properties are stored.
+        /// </summary>
+        /// <param name="root">Parent object.</param>
+        public static void SetMainRoot(object root)
+        {
+            MainRoot = root;
+        }
+
+        /// <summary>
+        ///     Adds property change to UndoStack.
+        /// </summary>
+        public static void AddUndoChange(Change change)
+        {
+            // Clears RedoStack if las move wasn't redo or undo and if redo stack is greater than 0
+            if (lastChangeWasUndo == false && RedoStack.Count > 0)
             {
                 RedoStack.Clear();
             }
 
-            lastChangeWasUndo = false;
-            change.Root ??= MainRoot;
-            UndoStack.Push(change);
-        }
-
-        /// <summary>
-        ///     Sets top property in UndoStack to Old Value
-        /// </summary>
-        public static void Undo()
-        {
-            lastChangeWasUndo = true;
-            Change change = UndoStack.Pop();
+            lastChangeWasUndo = false;
+            change.Root ??= MainRoot;
+            UndoStack.Push(change);
+        }
+
+        /// <summary>
+        ///     Sets top property in UndoStack to Old Value.
+        /// </summary>
+        public static void Undo()
+        {
+            lastChangeWasUndo = true;
+            Change change = UndoStack.Pop();
             if (change.ReverseProcess == null)
             {
                 SetPropertyValue(change.Root, change.Property, change.OldValue);
@@ -59,16 +59,16 @@ namespace PixiEditor.Models.Controllers
                 change.ReverseProcess(change.ReverseProcessArguments);
             }
 
-            RedoStack.Push(change);
-        }
-
-        /// <summary>
-        ///     Sets top property from RedoStack to old value
-        /// </summary>
-        public static void Redo()
-        {
-            lastChangeWasUndo = true;
-            Change change = RedoStack.Pop();
+            RedoStack.Push(change);
+        }
+
+        /// <summary>
+        ///     Sets top property from RedoStack to old value.
+        /// </summary>
+        public static void Redo()
+        {
+            lastChangeWasUndo = true;
+            Change change = RedoStack.Pop();
             if (change.Process == null)
             {
                 SetPropertyValue(change.Root, change.Property, change.NewValue);
@@ -78,20 +78,20 @@ namespace PixiEditor.Models.Controllers
                 change.Process(change.ProcessArguments);
             }
 
-            UndoStack.Push(change);
-        }
+            UndoStack.Push(change);
+        }
+
+        private static void SetPropertyValue(object target, string propName, object value)
+        {
+            string[] bits = propName.Split('.');
+            for (int i = 0; i < bits.Length - 1; i++)
+            {
+                System.Reflection.PropertyInfo propertyToGet = target.GetType().GetProperty(bits[i]);
+                target = propertyToGet.GetValue(target, null);
+            }
 
-        private static void SetPropertyValue(object target, string propName, object value)
-        {
-            string[] bits = propName.Split('.');
-            for (int i = 0; i < bits.Length - 1; i++)
-            {
-                System.Reflection.PropertyInfo propertyToGet = target.GetType().GetProperty(bits[i]);
-                target = propertyToGet.GetValue(target, null);
-            }
-
-            System.Reflection.PropertyInfo propertyToSet = target.GetType().GetProperty(bits.Last());
-            propertyToSet.SetValue(target, value, null);
-        }
-    }
+            System.Reflection.PropertyInfo propertyToSet = target.GetType().GetProperty(bits.Last());
+            propertyToSet.SetValue(target, value, null);
+        }
+    }
 }

+ 56 - 61
PixiEditor/Models/DataHolders/BitmapPixelChanges.cs

@@ -1,88 +1,83 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Media;
-using PixiEditor.Exceptions;
-using PixiEditor.Helpers.Extensions;
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.DataHolders
-{
-    public struct BitmapPixelChanges
-    {
-        public bool WasBuiltAsSingleColored { get; private set; }
-
-        public static BitmapPixelChanges Empty => new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Media;
+using PixiEditor.Exceptions;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Position;
 
-        public Dictionary<Coordinates, Color> ChangedPixels { get; set; }
-
-        public BitmapPixelChanges(Dictionary<Coordinates, Color> changedPixels)
-        {
-            ChangedPixels = changedPixels;
-            WasBuiltAsSingleColored = false;
-        }
-
-        /// <summary>
-        ///     Builds BitmapPixelChanges with only one color for specified coordinates
-        /// </summary>
-        /// <param name="coordinates"></param>
-        /// <param name="color"></param>
-        /// <returns>Single-colored BitmapPixelChanges</returns>
-        public static BitmapPixelChanges FromSingleColoredArray(IEnumerable<Coordinates> coordinates, Color color)
-        {
-            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
+namespace PixiEditor.Models.DataHolders
+{
+    public struct BitmapPixelChanges
+    {
+        public BitmapPixelChanges(Dictionary<Coordinates, Color> changedPixels)
+        {
+            ChangedPixels = changedPixels;
+            WasBuiltAsSingleColored = false;
+        }
+
+        public static BitmapPixelChanges Empty => new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
+
+        public bool WasBuiltAsSingleColored { get; private set; }
+
+        public Dictionary<Coordinates, Color> ChangedPixels { get; set; }
+
+        /// <summary>
+        ///     Builds BitmapPixelChanges with only one color for specified coordinates.
+        /// </summary>
+        /// <returns>Single-colored BitmapPixelChanges.</returns>
+        public static BitmapPixelChanges FromSingleColoredArray(IEnumerable<Coordinates> coordinates, Color color)
+        {
+            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
             foreach (Coordinates coordinate in coordinates)
             {
                 dict.Add(coordinate, color);
             }
 
-            return new BitmapPixelChanges(dict) { WasBuiltAsSingleColored = true };
-        }
-
-        /// <summary>
-        ///     Combines pixel changes array with overriding values.
-        /// </summary>
-        /// <param name="changes">BitmapPixelChanges to combine</param>
-        /// <returns>Combined BitmapPixelChanges</returns>
-        public static BitmapPixelChanges CombineOverride(BitmapPixelChanges[] changes)
-        {
+            return new BitmapPixelChanges(dict) { WasBuiltAsSingleColored = true };
+        }
+
+        /// <summary>
+        ///     Combines pixel changes array with overriding values.
+        /// </summary>
+        /// <param name="changes">BitmapPixelChanges to combine.</param>
+        /// <returns>Combined BitmapPixelChanges.</returns>
+        public static BitmapPixelChanges CombineOverride(BitmapPixelChanges[] changes)
+        {
             if (changes == null || changes.Length == 0)
             {
                 throw new ArgumentException();
             }
 
-            BitmapPixelChanges output = Empty;
-
+            BitmapPixelChanges output = Empty;
+
             for (int i = 0; i < changes.Length; i++)
             {
                 output.ChangedPixels.AddRangeOverride(changes[i].ChangedPixels);
             }
 
-            return output;
-        }
-
-        /// <summary>
-        ///     Builds BitmapPixelChanges using 2 same-length enumerables of coordinates and colors
-        /// </summary>
-        /// <param name="coordinates"></param>
-        /// <param name="color"></param>
-        /// <returns></returns>
-        public static BitmapPixelChanges FromArrays(IEnumerable<Coordinates> coordinates, IEnumerable<Color> color)
-        {
-            Coordinates[] coordinateArray = coordinates.ToArray();
-            Color[] colorArray = color.ToArray();
+            return output;
+        }
+
+        /// <summary>
+        ///     Builds BitmapPixelChanges using 2 same-length enumerables of coordinates and colors.
+        /// </summary>
+        public static BitmapPixelChanges FromArrays(IEnumerable<Coordinates> coordinates, IEnumerable<Color> color)
+        {
+            Coordinates[] coordinateArray = coordinates.ToArray();
+            Color[] colorArray = color.ToArray();
             if (coordinateArray.Length != colorArray.Length)
             {
                 throw new ArrayLengthMismatchException();
             }
 
-            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
+            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
             for (int i = 0; i < coordinateArray.Length; i++)
             {
                 dict.Add(coordinateArray[i], colorArray[i]);
             }
 
-            return new BitmapPixelChanges(dict);
-        }
-    }
+            return new BitmapPixelChanges(dict);
+        }
+    }
 }

+ 95 - 78
PixiEditor/Models/DataHolders/Change.cs

@@ -1,81 +1,98 @@
-using System;
-
-namespace PixiEditor.Models.DataHolders
-{
-    [Serializable]
-    public class Change
-    {
-        public object[] ProcessArguments;
-        public object[] ReverseProcessArguments;
-
-        /// <summary>
-        ///     Creates new change for property based undo system
-        /// </summary>
-        /// <param name="property">Name of property</param>
-        /// <param name="oldValue">Old value of property</param>
-        /// <param name="newValue">New value of property</param>
-        /// <param name="description">Description of change</param>
-        /// <param name="root">Custom root for finding property</param>
-        public Change(string property, object oldValue, object newValue,
-            string description = "", object root = null)
-        {
-            Property = property;
-            OldValue = oldValue;
-            Description = description;
-            NewValue = newValue;
-            Root = root;
-        }
-
-        /// <summary>
-        ///     Creates new change for mixed reverse process based system with new value property based system
-        /// </summary>
-        /// <param name="property">Name of property, which new value will be applied to</param>
-        /// <param name="reverseProcess">Method with reversing value process</param>
-        /// <param name="reverseArguments">Arguments for reverse method</param>
-        /// <param name="newValue">New value of property</param>
-        /// <param name="description">Description of change</param>
-        /// <param name="root">Custom root for finding property</param>
-        public Change(string property, Action<object[]> reverseProcess, object[] reverseArguments,
-            object newValue, string description = "", object root = null)
-        {
-            Property = property;
-            ReverseProcess = reverseProcess;
-            ReverseProcessArguments = reverseArguments;
-            NewValue = newValue;
-            Description = description;
-            Root = root;
-        }
-
-        /// <summary>
-        ///     Creates new change for reverse process based system
-        /// </summary>
-        /// <param name="reverseProcess">Method with reversing value process</param>
-        /// <param name="reverseArguments">Arguments for reverse method</param>
-        /// <param name="process">Method with reversing the reversed value</param>
-        /// <param name="processArguments">Arguments for process method</param>
-        /// <param name="description">Description of change</param>
-        public Change(Action<object[]> reverseProcess, object[] reverseArguments,
-            Action<object[]> process, object[] processArguments, string description = "")
-        {
-            ReverseProcess = reverseProcess;
-            ReverseProcessArguments = reverseArguments;
-            Process = process;
-            ProcessArguments = processArguments;
-            Description = description;
-        }
-
-        public object OldValue { get; set; }
-
-        public object NewValue { get; set; }
-
-        public string Description { get; set; }
-
-        public string Property { get; set; }
-
-        public Action<object[]> ReverseProcess { get; set; }
+using System;
 
-        public Action<object[]> Process { get; set; }
+namespace PixiEditor.Models.DataHolders
+{
+    [Serializable]
+    public class Change
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Change"/> class.
+        ///     Creates new change for property based undo system.
+        /// </summary>
+        /// <param name="property">Name of property.</param>
+        /// <param name="oldValue">Old value of property.</param>
+        /// <param name="newValue">New value of property.</param>
+        /// <param name="description">Description of change.</param>
+        /// <param name="root">Custom root for finding property.</param>
+        public Change(
+            string property,
+            object oldValue,
+            object newValue,
+            string description = "",
+            object root = null)
+        {
+            Property = property;
+            OldValue = oldValue;
+            Description = description;
+            NewValue = newValue;
+            Root = root;
+        }
 
-        public object Root { get; set; }
-    }
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Change"/> class.
+        ///     Creates new change for mixed reverse process based system with new value property based system.
+        /// </summary>
+        /// <param name="property">Name of property, which new value will be applied to.</param>
+        /// <param name="reverseProcess">Method with reversing value process.</param>
+        /// <param name="reverseArguments">Arguments for reverse method.</param>
+        /// <param name="newValue">New value of property.</param>
+        /// <param name="description">Description of change.</param>
+        /// <param name="root">Custom root for finding property.</param>
+        public Change(
+            string property,
+            Action<object[]> reverseProcess,
+            object[] reverseArguments,
+            object newValue,
+            string description = "",
+            object root = null)
+        {
+            Property = property;
+            ReverseProcess = reverseProcess;
+            ReverseProcessArguments = reverseArguments;
+            NewValue = newValue;
+            Description = description;
+            Root = root;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Change"/> class.
+        ///     Creates new change for reverse process based system.
+        /// </summary>
+        /// <param name="reverseProcess">Method with reversing value process.</param>
+        /// <param name="reverseArguments">Arguments for reverse method.</param>
+        /// <param name="process">Method with reversing the reversed value.</param>
+        /// <param name="processArguments">Arguments for process method.</param>
+        /// <param name="description">Description of change.</param>
+        public Change(
+            Action<object[]> reverseProcess,
+            object[] reverseArguments,
+            Action<object[]> process,
+            object[] processArguments,
+            string description = "")
+        {
+            ReverseProcess = reverseProcess;
+            ReverseProcessArguments = reverseArguments;
+            Process = process;
+            ProcessArguments = processArguments;
+            Description = description;
+        }
+
+        public object[] ProcessArguments { get; set; }
+
+        public object[] ReverseProcessArguments { get; set; }
+
+        public object OldValue { get; set; }
+
+        public object NewValue { get; set; }
+
+        public string Description { get; set; }
+
+        public string Property { get; set; }
+
+        public Action<object[]> ReverseProcess { get; set; }
+
+        public Action<object[]> Process { get; set; }
+
+        public object Root { get; set; }
+    }
 }

+ 284 - 284
PixiEditor/Models/DataHolders/Document.cs

@@ -1,102 +1,193 @@
-using System;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media;
-using PixiEditor.Helpers;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.DataHolders
-{
-    public class Document : NotifyableObject
-    {
-        private int activeLayerIndex;
-        private int height;
-        private int width;
-
-        public Document(int width, int height)
-        {
-            Width = width;
-            Height = height;
-        }
-
-        public int Width
-        {
-            get => width;
-            set
-            {
-                width = value;
-                RaisePropertyChanged("Width");
-            }
-        }
-
-        public int Height
-        {
-            get => height;
-            set
-            {
-                height = value;
-                RaisePropertyChanged("Height");
-            }
-        }
-
-        public ObservableCollection<Layer> Layers { get; set; } = new ObservableCollection<Layer>();
-
-        public Layer ActiveLayer => Layers.Count > 0 ? Layers[ActiveLayerIndex] : null;
-
-        public int ActiveLayerIndex
-        {
-            get => activeLayerIndex;
-            set
-            {
-                activeLayerIndex = value;
-                RaisePropertyChanged("ActiveLayerIndex");
-                RaisePropertyChanged("ActiveLayer");
-            }
-        }
-
-        public ObservableCollection<Color> Swatches { get; set; } = new ObservableCollection<Color>();
-
-        public event EventHandler<DocumentSizeChangedEventArgs> DocumentSizeChanged;
-
-        /// <summary>
-        ///     Resizes canvas to specified width and height to selected anchor
-        /// </summary>
-        /// <param name="width">New width of canvas</param>
-        /// <param name="height">New height of canvas</param>
-        /// <param name="anchor">
-        ///     Point that will act as "starting position" of resizing. Use pipe to connect horizontal and
-        ///     vertical.
-        /// </param>
-        public void ResizeCanvas(int width, int height, AnchorPoint anchor)
-        {
-            int oldWidth = Width;
-            int oldHeight = Height;
-
-            int offsetX = GetOffsetXForAnchor(Width, width, anchor);
-            int offsetY = GetOffsetYForAnchor(Height, height, anchor);
-
-            Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
-            Thickness[] newOffsets = Layers.Select(x => new Thickness(offsetX + x.OffsetX, offsetY + x.OffsetY, 0, 0))
-                .ToArray();
-
-            object[] processArgs = { newOffsets, width, height };
-            object[] reverseProcessArgs = { oldOffsets, Width, Height };
-
-            ResizeCanvas(newOffsets, width, height);
-            UndoManager.AddUndoChange(new Change(ResizeCanvasProcess,
-                reverseProcessArgs, ResizeCanvasProcess, processArgs, "Resize canvas"));
-            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
-        }
-
-        private int GetOffsetXForAnchor(int srcWidth, int destWidth, AnchorPoint anchor)
-        {
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public class Document : NotifyableObject
+    {
+        private int activeLayerIndex;
+        private int height;
+        private int width;
+
+        public Document(int width, int height)
+        {
+            Width = width;
+            Height = height;
+        }
+
+        public event EventHandler<DocumentSizeChangedEventArgs> DocumentSizeChanged;
+
+        public int Width
+        {
+            get => width;
+            set
+            {
+                width = value;
+                RaisePropertyChanged("Width");
+            }
+        }
+
+        public int Height
+        {
+            get => height;
+            set
+            {
+                height = value;
+                RaisePropertyChanged("Height");
+            }
+        }
+
+        public ObservableCollection<Layer> Layers { get; set; } = new ObservableCollection<Layer>();
+
+        public Layer ActiveLayer => Layers.Count > 0 ? Layers[ActiveLayerIndex] : null;
+
+        public int ActiveLayerIndex
+        {
+            get => activeLayerIndex;
+            set
+            {
+                activeLayerIndex = value;
+                RaisePropertyChanged("ActiveLayerIndex");
+                RaisePropertyChanged("ActiveLayer");
+            }
+        }
+
+        public ObservableCollection<Color> Swatches { get; set; } = new ObservableCollection<Color>();
+
+        /// <summary>
+        ///     Resizes canvas to specified width and height to selected anchor.
+        /// </summary>
+        /// <param name="width">New width of canvas.</param>
+        /// <param name="height">New height of canvas.</param>
+        /// <param name="anchor">
+        ///     Point that will act as "starting position" of resizing. Use pipe to connect horizontal and
+        ///     vertical.
+        /// </param>
+        public void ResizeCanvas(int width, int height, AnchorPoint anchor)
+        {
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            int offsetX = GetOffsetXForAnchor(Width, width, anchor);
+            int offsetY = GetOffsetYForAnchor(Height, height, anchor);
+
+            Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
+            Thickness[] newOffsets = Layers.Select(x => new Thickness(offsetX + x.OffsetX, offsetY + x.OffsetY, 0, 0))
+                .ToArray();
+
+            object[] processArgs = { newOffsets, width, height };
+            object[] reverseProcessArgs = { oldOffsets, Width, Height };
+
+            ResizeCanvas(newOffsets, width, height);
+            UndoManager.AddUndoChange(new Change(
+                ResizeCanvasProcess,
+                reverseProcessArgs,
+                ResizeCanvasProcess,
+                processArgs,
+                "Resize canvas"));
+            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
+        }
+
+        /// <summary>
+        ///     Resizes all document layers using NearestNeighbor interpolation.
+        /// </summary>
+        /// <param name="newWidth">New document width.</param>
+        /// <param name="newHeight">New document height.</param>
+        public void Resize(int newWidth, int newHeight)
+        {
+            object[] reverseArgs = { Width, Height };
+            object[] args = { newWidth, newHeight };
+            ResizeDocument(args);
+            UndoManager.AddUndoChange(new Change(
+                ResizeDocument,
+                reverseArgs,
+                ResizeDocument,
+                args,
+                "Resize document"));
+        }
+
+        /// <summary>
+        ///     Resizes canvas, so it fits exactly the size of drawn content, without any transparent pixels outside.
+        /// </summary>
+        public void ClipCanvas()
+        {
+            DoubleCords points = GetEdgePoints();
+            int smallestX = points.Coords1.X;
+            int smallestY = points.Coords1.Y;
+            int biggestX = points.Coords2.X;
+            int biggestY = points.Coords2.Y;
+
+            if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
+            {
+                return;
+            }
+
+            int width = biggestX - smallestX;
+            int height = biggestY - smallestY;
+            Coordinates moveVector = new Coordinates(-smallestX, -smallestY);
+
+            Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            MoveOffsets(moveVector);
+            Width = width;
+            Height = height;
+
+            object[] reverseArguments = { oldOffsets, oldWidth, oldHeight };
+            object[] processArguments = { Layers.Select(x => x.Offset).ToArray(), width, height };
+
+            UndoManager.AddUndoChange(new Change(
+                ResizeCanvasProcess,
+                reverseArguments,
+                ResizeCanvasProcess,
+                processArguments,
+                "Clip canvas"));
+        }
+
+        public void CenterContent()
+        {
+            DoubleCords points = GetEdgePoints();
+
+            int smallestX = points.Coords1.X;
+            int smallestY = points.Coords1.Y;
+            int biggestX = points.Coords2.X;
+            int biggestY = points.Coords2.Y;
+
+            if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
+            {
+                return;
+            }
+
+            Coordinates contentCenter = CoordinatesCalculator.GetCenterPoint(points.Coords1, points.Coords2);
+            Coordinates documentCenter = CoordinatesCalculator.GetCenterPoint(
+                new Coordinates(0, 0),
+                new Coordinates(Width, Height));
+            Coordinates moveVector = new Coordinates(documentCenter.X - contentCenter.X, documentCenter.Y - contentCenter.Y);
+
+            MoveOffsets(moveVector);
+            UndoManager.AddUndoChange(
+                new Change(
+                    MoveOffsetsProcess,
+                    new object[] { new Coordinates(-moveVector.X, -moveVector.Y) },
+                    MoveOffsetsProcess,
+                    new object[] { moveVector },
+                    "Center content"));
+        }
+
+        private int GetOffsetXForAnchor(int srcWidth, int destWidth, AnchorPoint anchor)
+        {
             if (anchor.HasFlag(AnchorPoint.Center))
             {
-                return Math.Abs(destWidth / 2 - srcWidth / 2);
+                return Math.Abs((destWidth / 2) - (srcWidth / 2));
             }
 
             if (anchor.HasFlag(AnchorPoint.Right))
@@ -104,14 +195,14 @@ namespace PixiEditor.Models.DataHolders
                 return Math.Abs(destWidth - srcWidth);
             }
 
-            return 0;
-        }
-
-        private int GetOffsetYForAnchor(int srcHeight, int destHeight, AnchorPoint anchor)
-        {
+            return 0;
+        }
+
+        private int GetOffsetYForAnchor(int srcHeight, int destHeight, AnchorPoint anchor)
+        {
             if (anchor.HasFlag(AnchorPoint.Middle))
             {
-                return Math.Abs(destHeight / 2 - srcHeight / 2);
+                return Math.Abs((destHeight / 2) - (srcHeight / 2));
             }
 
             if (anchor.HasFlag(AnchorPoint.Bottom))
@@ -119,125 +210,77 @@ namespace PixiEditor.Models.DataHolders
                 return Math.Abs(destHeight - srcHeight);
             }
 
-            return 0;
-        }
-
-        /// <summary>
-        ///     Resizes all document layers using NearestNeighbor interpolation.
-        /// </summary>
-        /// <param name="newWidth">New document width</param>
-        /// <param name="newHeight">New document height</param>
-        public void Resize(int newWidth, int newHeight)
-        {
-            object[] reverseArgs = { Width, Height };
-            object[] args = { newWidth, newHeight };
-            ResizeDocument(args);
-            UndoManager.AddUndoChange(new Change(ResizeDocument, reverseArgs,
-                ResizeDocument, args, "Resize document"));
-        }
-
-        private void ResizeDocument(object[] arguments)
-        {
-            int oldWidth = Width;
-            int oldHeight = Height;
-
-            int newWidth = (int)arguments[0];
-            int newHeight = (int)arguments[1];
-
-            for (int i = 0; i < Layers.Count; i++)
-            {
-                float widthRatio = (float)newWidth / Width;
-                float heightRatio = (float)newHeight / Height;
-                int layerWidth = (int)(Layers[i].Width * widthRatio);
-                int layerHeight = (int)(Layers[i].Height * heightRatio);
-
-                Layers[i].Resize(layerWidth, layerHeight, newWidth, newHeight);
-                Layers[i].Offset = new Thickness(Layers[i].OffsetX * widthRatio, Layers[i].OffsetY * heightRatio, 0, 0);
-            }
-
-            Height = newHeight;
-            Width = newWidth;
-            DocumentSizeChanged?.Invoke(this,
-                new DocumentSizeChangedEventArgs(oldWidth, oldHeight, newWidth, newHeight));
-        }
-
-        private void ResizeCanvasProcess(object[] arguments)
-        {
-            int oldWidth = Width;
-            int oldHeight = Height;
-
-            Thickness[] offset = (Thickness[])arguments[0];
-            int width = (int)arguments[1];
-            int height = (int)arguments[2];
-            ResizeCanvas(offset, width, height);
-            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
-        }
-
-        /// <summary>
-        ///     Resizes canvas
-        /// </summary>
-        /// <param name="offset">Offset of content in new canvas. It will move layer to that offset</param>
-        /// <param name="newWidth">New canvas size.</param>
-        /// <param name="newHeight">New canvas height.</param>
-        private void ResizeCanvas(Thickness[] offset, int newWidth, int newHeight)
-        {
-            for (int i = 0; i < Layers.Count; i++)
-            {
-                Layers[i].Offset = offset[i];
-                Layers[i].MaxWidth = newWidth;
-                Layers[i].MaxHeight = newHeight;
-            }
-
-            Width = newWidth;
-            Height = newHeight;
-        }
-
-        /// <summary>
-        ///     Resizes canvas, so it fits exactly the size of drawn content, without any transparent pixels outside.
-        /// </summary>
-        public void ClipCanvas()
-        {
-            DoubleCords points = GetEdgePoints();
-            int smallestX = points.Coords1.X;
-            int smallestY = points.Coords1.Y;
-            int biggestX = points.Coords2.X;
-            int biggestY = points.Coords2.Y;
-
-            if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
+            return 0;
+        }
+
+        private void ResizeDocument(object[] arguments)
+        {
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            int newWidth = (int)arguments[0];
+            int newHeight = (int)arguments[1];
+
+            for (int i = 0; i < Layers.Count; i++)
             {
-                return;
+                float widthRatio = (float)newWidth / Width;
+                float heightRatio = (float)newHeight / Height;
+                int layerWidth = (int)(Layers[i].Width * widthRatio);
+                int layerHeight = (int)(Layers[i].Height * heightRatio);
+
+                Layers[i].Resize(layerWidth, layerHeight, newWidth, newHeight);
+                Layers[i].Offset = new Thickness(Layers[i].OffsetX * widthRatio, Layers[i].OffsetY * heightRatio, 0, 0);
+            }
+
+            Height = newHeight;
+            Width = newWidth;
+            DocumentSizeChanged?.Invoke(
+                this,
+                new DocumentSizeChangedEventArgs(oldWidth, oldHeight, newWidth, newHeight));
+        }
+
+        private void ResizeCanvasProcess(object[] arguments)
+        {
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            Thickness[] offset = (Thickness[])arguments[0];
+            int width = (int)arguments[1];
+            int height = (int)arguments[2];
+            ResizeCanvas(offset, width, height);
+            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
+        }
+
+        /// <summary>
+        ///     Resizes canvas.
+        /// </summary>
+        /// <param name="offset">Offset of content in new canvas. It will move layer to that offset.</param>
+        /// <param name="newWidth">New canvas size.</param>
+        /// <param name="newHeight">New canvas height.</param>
+        private void ResizeCanvas(Thickness[] offset, int newWidth, int newHeight)
+        {
+            for (int i = 0; i < Layers.Count; i++)
+            {
+                Layers[i].Offset = offset[i];
+                Layers[i].MaxWidth = newWidth;
+                Layers[i].MaxHeight = newHeight;
             }
 
-            int width = biggestX - smallestX;
-            int height = biggestY - smallestY;
-            Coordinates moveVector = new Coordinates(-smallestX, -smallestY);
-
-            Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
-            int oldWidth = Width;
-            int oldHeight = Height;
-
-            MoveOffsets(moveVector);
-            Width = width;
-            Height = height;
-
-            object[] reverseArguments = { oldOffsets, oldWidth, oldHeight };
-            object[] processArguments = { Layers.Select(x => x.Offset).ToArray(), width, height };
-
-            UndoManager.AddUndoChange(new Change(ResizeCanvasProcess, reverseArguments,
-                ResizeCanvasProcess, processArguments, "Clip canvas"));
-        }
-
-        private DoubleCords GetEdgePoints()
-        {
-            Layer firstLayer = Layers[0];
-            int smallestX = firstLayer.OffsetX;
-            int smallestY = firstLayer.OffsetY;
-            int biggestX = smallestX + firstLayer.Width;
-            int biggestY = smallestY + firstLayer.Height;
-
-            for (int i = 0; i < Layers.Count; i++)
-            {
-                Layers[i].ClipCanvas();
+            Width = newWidth;
+            Height = newHeight;
+        }
+
+        private DoubleCords GetEdgePoints()
+        {
+            Layer firstLayer = Layers[0];
+            int smallestX = firstLayer.OffsetX;
+            int smallestY = firstLayer.OffsetY;
+            int biggestX = smallestX + firstLayer.Width;
+            int biggestY = smallestY + firstLayer.Height;
+
+            for (int i = 0; i < Layers.Count; i++)
+            {
+                Layers[i].ClipCanvas();
                 if (Layers[i].OffsetX < smallestX)
                 {
                     smallestX = Layers[i].OffsetX;
@@ -257,72 +300,29 @@ namespace PixiEditor.Models.DataHolders
                 {
                     biggestY = Layers[i].OffsetY + Layers[i].Height;
                 }
-            }
-
-            return new DoubleCords(new Coordinates(smallestX, smallestY),
-                new Coordinates(biggestX, biggestY));
-        }
-
-        /// <summary>
-        ///     Moves offsets of layers by specified vector.
-        /// </summary>
-        /// <param name="moveVector"></param>
-        private void MoveOffsets(Coordinates moveVector)
-        {
-            for (int i = 0; i < Layers.Count; i++)
-            {
-                Thickness offset = Layers[i].Offset;
-                Layers[i].Offset = new Thickness(offset.Left + moveVector.X, offset.Top + moveVector.Y, 0, 0);
-            }
-        }
-
-        private void MoveOffsetsProcess(object[] arguments)
-        {
-            Coordinates vector = (Coordinates)arguments[0];
-            MoveOffsets(vector);
-        }
-
-        public void CenterContent()
-        {
-            DoubleCords points = GetEdgePoints();
-
-            int smallestX = points.Coords1.X;
-            int smallestY = points.Coords1.Y;
-            int biggestX = points.Coords2.X;
-            int biggestY = points.Coords2.Y;
-
-            if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
+            }
+
+            return new DoubleCords(
+                new Coordinates(smallestX, smallestY),
+                new Coordinates(biggestX, biggestY));
+        }
+
+        /// <summary>
+        ///     Moves offsets of layers by specified vector.
+        /// </summary>
+        private void MoveOffsets(Coordinates moveVector)
+        {
+            for (int i = 0; i < Layers.Count; i++)
             {
-                return;
+                Thickness offset = Layers[i].Offset;
+                Layers[i].Offset = new Thickness(offset.Left + moveVector.X, offset.Top + moveVector.Y, 0, 0);
             }
+        }
 
-            Coordinates contentCenter = CoordinatesCalculator.GetCenterPoint(points.Coords1, points.Coords2);
-            Coordinates documentCenter = CoordinatesCalculator.GetCenterPoint(new Coordinates(0, 0),
-                new Coordinates(Width, Height));
-            Coordinates moveVector = new Coordinates(documentCenter.X - contentCenter.X, documentCenter.Y - contentCenter.Y);
-
-            MoveOffsets(moveVector);
-            UndoManager.AddUndoChange(new Change(MoveOffsetsProcess, new object[] { new Coordinates(-moveVector.X, -moveVector.Y) }, MoveOffsetsProcess,
-                new object[] { moveVector }, "Center content"));
-        }
-    }
-
-    public class DocumentSizeChangedEventArgs
-    {
-        public DocumentSizeChangedEventArgs(int oldWidth, int oldHeight, int newWidth, int newHeight)
-        {
-            OldWidth = oldWidth;
-            OldHeight = oldHeight;
-            NewWidth = newWidth;
-            NewHeight = newHeight;
-        }
-
-        public int OldWidth { get; set; }
-
-        public int OldHeight { get; set; }
-
-        public int NewWidth { get; set; }
-
-        public int NewHeight { get; set; }
-    }
+        private void MoveOffsetsProcess(object[] arguments)
+        {
+            Coordinates vector = (Coordinates)arguments[0];
+            MoveOffsets(vector);
+        }
+    }
 }

+ 21 - 0
PixiEditor/Models/DataHolders/DocumentSizeChangedEventArgs.cs

@@ -0,0 +1,21 @@
+namespace PixiEditor.Models.DataHolders
+{
+    public class DocumentSizeChangedEventArgs
+    {
+        public DocumentSizeChangedEventArgs(int oldWidth, int oldHeight, int newWidth, int newHeight)
+        {
+            OldWidth = oldWidth;
+            OldHeight = oldHeight;
+            NewWidth = newWidth;
+            NewHeight = newHeight;
+        }
+
+        public int OldWidth { get; set; }
+
+        public int OldHeight { get; set; }
+
+        public int NewWidth { get; set; }
+
+        public int NewHeight { get; set; }
+    }
+}

+ 92 - 93
PixiEditor/Models/IO/Exporter.cs

@@ -1,98 +1,97 @@
-using System;
-using System.IO;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using Microsoft.Win32;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Dialogs;
-
-namespace PixiEditor.Models.IO
-{
-    public class Exporter
-    {
-        public static Size FileDimensions;
-
-        public static string SaveDocumentPath { get; set; }
-
-        /// <summary>
-        ///     Saves document as .pixi file that contains all document data
-        /// </summary>
-        /// <param name="document">Document to save</param>
-        /// <param name="updateWorkspacePath">Should editor remember dialog path for further saves</param>
-        public static bool SaveAsEditableFileWithDialog(Document document, bool updateWorkspacePath = false)
-        {
-            SaveFileDialog dialog = new SaveFileDialog
-            {
-                Filter = "PixiEditor Files | *.pixi",
-                DefaultExt = "pixi"
-            };
-            if ((bool)dialog.ShowDialog())
-            {
-                SaveAsEditableFile(document, dialog.FileName, updateWorkspacePath);
-                return true;
-            }
-
-            return false;
-        }
-
-        public static void SaveAsEditableFile(Document document, string path, bool updateWorkspacePath = false)
-        {
-            BinarySerialization.WriteToBinaryFile(path, new SerializableDocument(document));
-
+using System;
+using System.IO;
+using System.Windows;
+using System.Windows.Media.Imaging;
+using Microsoft.Win32;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Dialogs;
+
+namespace PixiEditor.Models.IO
+{
+    public class Exporter
+    {
+        public static Size FileDimensions { get; set; }
+
+        public static string SaveDocumentPath { get; set; }
+
+        /// <summary>
+        ///     Saves document as .pixi file that contains all document data.
+        /// </summary>
+        /// <param name="document">Document to save.</param>
+        /// <param name="updateWorkspacePath">Should editor remember dialog path for further saves.</param>
+        public static bool SaveAsEditableFileWithDialog(Document document, bool updateWorkspacePath = false)
+        {
+            SaveFileDialog dialog = new SaveFileDialog
+            {
+                Filter = "PixiEditor Files | *.pixi",
+                DefaultExt = "pixi"
+            };
+            if ((bool)dialog.ShowDialog())
+            {
+                SaveAsEditableFile(document, dialog.FileName, updateWorkspacePath);
+                return true;
+            }
+
+            return false;
+        }
+
+        public static void SaveAsEditableFile(Document document, string path, bool updateWorkspacePath = false)
+        {
+            BinarySerialization.WriteToBinaryFile(path, new SerializableDocument(document));
+
             if (updateWorkspacePath)
             {
                 SaveDocumentPath = path;
             }
-        }
-
-        /// <summary>
-        ///     Creates ExportFileDialog to get width, height and path of file.
-        /// </summary>
-        /// <param name="bitmap">Bitmap to be saved as file.</param>
-        /// <param name="fileDimensions">Size of file</param>
-        public static void Export(WriteableBitmap bitmap, Size fileDimensions)
-        {
-            ExportFileDialog info = new ExportFileDialog(fileDimensions);
-            // If OK on dialog has been clicked
-            if (info.ShowDialog())
-            {
-                // If sizes are incorrect
-                if (info.FileWidth < bitmap.Width || info.FileHeight < bitmap.Height)
-                {
-                    MessageBox.Show("Incorrect height or width value", "Error", MessageBoxButton.OK,
-                        MessageBoxImage.Error);
-                    return;
-                }
-
-                FileDimensions = new Size(info.FileWidth, info.FileHeight);
-                SaveAsPng(info.FilePath, info.FileWidth, info.FileHeight, bitmap);
-            }
-        }
-
-        /// <summary>
-        ///     Saves image to PNG file
-        /// </summary>
-        /// <param name="savePath">Save file path</param>
-        /// <param name="exportWidth">File width</param>
-        /// <param name="exportHeight">File height</param>
-        /// <param name="bitmap">Bitmap to save</param>
-        public static void SaveAsPng(string savePath, int exportWidth, int exportHeight, WriteableBitmap bitmap)
-        {
-            try
-            {
-                bitmap = bitmap.Resize(exportWidth, exportHeight,
-                    WriteableBitmapExtensions.Interpolation.NearestNeighbor);
-                using (FileStream stream = new FileStream(savePath, FileMode.Create))
-                {
-                    PngBitmapEncoder encoder = new PngBitmapEncoder();
-                    encoder.Frames.Add(BitmapFrame.Create(bitmap));
-                    encoder.Save(stream);
-                }
-            }
-            catch (Exception err)
-            {
-                MessageBox.Show(err.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
-            }
-        }
-    }
+        }
+
+        /// <summary>
+        ///     Creates ExportFileDialog to get width, height and path of file.
+        /// </summary>
+        /// <param name="bitmap">Bitmap to be saved as file.</param>
+        /// <param name="fileDimensions">Size of file.</param>
+        public static void Export(WriteableBitmap bitmap, Size fileDimensions)
+        {
+            ExportFileDialog info = new ExportFileDialog(fileDimensions);
+
+            // If OK on dialog has been clicked
+            if (info.ShowDialog())
+            {
+                // If sizes are incorrect
+                if (info.FileWidth < bitmap.Width || info.FileHeight < bitmap.Height)
+                {
+                    MessageBox.Show("Incorrect height or width value", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+                    return;
+                }
+
+                FileDimensions = new Size(info.FileWidth, info.FileHeight);
+                SaveAsPng(info.FilePath, info.FileWidth, info.FileHeight, bitmap);
+            }
+        }
+
+        /// <summary>
+        ///     Saves image to PNG file.
+        /// </summary>
+        /// <param name="savePath">Save file path.</param>
+        /// <param name="exportWidth">File width.</param>
+        /// <param name="exportHeight">File height.</param>
+        /// <param name="bitmap">Bitmap to save.</param>
+        public static void SaveAsPng(string savePath, int exportWidth, int exportHeight, WriteableBitmap bitmap)
+        {
+            try
+            {
+                bitmap = bitmap.Resize(exportWidth, exportHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+                using (FileStream stream = new FileStream(savePath, FileMode.Create))
+                {
+                    PngBitmapEncoder encoder = new PngBitmapEncoder();
+                    encoder.Frames.Add(BitmapFrame.Create(bitmap));
+                    encoder.Save(stream);
+                }
+            }
+            catch (Exception err)
+            {
+                MessageBox.Show(err.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+        }
+    }
 }

+ 2 - 2
PixiEditor/Models/IO/Importer.cs

@@ -8,7 +8,7 @@ namespace PixiEditor.Models.IO
     public class Importer : NotifyableObject
     {
         /// <summary>
-        ///     Imports image from path and resizes it to given dimensions
+        ///     Imports image from path and resizes it to given dimensions.
         /// </summary>
         /// <param name="path">Path of image.</param>
         /// <param name="width">New width of image.</param>
@@ -26,7 +26,7 @@ namespace PixiEditor.Models.IO
         }
 
         /// <summary>
-        ///     Imports image from path and resizes it to given dimensions
+        ///     Imports image from path and resizes it to given dimensions.
         /// </summary>
         /// <param name="path">Path of image.</param>
         public static WriteableBitmap ImportImage(string path)

+ 65 - 66
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -1,41 +1,40 @@
-using System.Collections.Generic;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.ImageManipulation
-{
-    public static class BitmapUtils
-    {
-        /// <summary>
-        ///     Converts pixel bytes to WriteableBitmap
-        /// </summary>
-        /// <param name="currentBitmapWidth">Width of bitmap</param>
-        /// <param name="currentBitmapHeight">Height of bitmap</param>
-        /// <param name="byteArray">Bitmap byte array</param>
-        /// <returns>WriteableBitmap</returns>
-        public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight,
-            byte[] byteArray)
-        {
-            WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
-            bitmap.FromByteArray(byteArray);
-            return bitmap;
-        }
-
-        /// <summary>
-        ///     Converts layers bitmaps into one bitmap.
-        /// </summary>
-        /// <param name="layers">Layers to combine</param>
-        /// <param name="width">Width of final bitmap</param>
-        /// <param name="height">Height of final bitmap</param>
-        /// <returns>WriteableBitmap of layered bitmaps</returns>
-        public static WriteableBitmap CombineLayers(Layer[] layers, int width, int height)
-        {
-            WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
-
-            using (finalBitmap.GetBitmapContext())
-            {
+using System.Collections.Generic;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.ImageManipulation
+{
+    public static class BitmapUtils
+    {
+        /// <summary>
+        ///     Converts pixel bytes to WriteableBitmap.
+        /// </summary>
+        /// <param name="currentBitmapWidth">Width of bitmap.</param>
+        /// <param name="currentBitmapHeight">Height of bitmap.</param>
+        /// <param name="byteArray">Bitmap byte array.</param>
+        /// <returns>WriteableBitmap.</returns>
+        public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight, byte[] byteArray)
+        {
+            WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
+            bitmap.FromByteArray(byteArray);
+            return bitmap;
+        }
+
+        /// <summary>
+        ///     Converts layers bitmaps into one bitmap.
+        /// </summary>
+        /// <param name="layers">Layers to combine.</param>
+        /// <param name="width">Width of final bitmap.</param>
+        /// <param name="height">Height of final bitmap.</param>
+        /// <returns>WriteableBitmap of layered bitmaps.</returns>
+        public static WriteableBitmap CombineLayers(Layer[] layers, int width, int height)
+        {
+            WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
+
+            using (finalBitmap.GetBitmapContext())
+            {
                 for (int i = 0; i < layers.Length; i++)
                 {
                     for (int y = 0; y < finalBitmap.Height; y++)
@@ -51,38 +50,38 @@ namespace PixiEditor.Models.ImageManipulation
                         }
                     }
                 }
-            }
-
-            return finalBitmap;
-        }
-
-        public static Dictionary<Layer, Color[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
-        {
-            Dictionary<Layer, Color[]> result = new Dictionary<Layer, Color[]>();
-
-            for (int i = 0; i < layers.Length; i++)
-            {
-                Color[] pixels = new Color[selection.Length];
-
-                using (layers[i].LayerBitmap.GetBitmapContext())
-                {
-                    for (int j = 0; j < pixels.Length; j++)
-                    {
-                        Coordinates position = layers[i].GetRelativePosition(selection[j]);
-                        if (position.X < 0 || position.X > layers[i].Width - 1 || position.Y < 0 ||
+            }
+
+            return finalBitmap;
+        }
+
+        public static Dictionary<Layer, Color[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
+        {
+            Dictionary<Layer, Color[]> result = new Dictionary<Layer, Color[]>();
+
+            for (int i = 0; i < layers.Length; i++)
+            {
+                Color[] pixels = new Color[selection.Length];
+
+                using (layers[i].LayerBitmap.GetBitmapContext())
+                {
+                    for (int j = 0; j < pixels.Length; j++)
+                    {
+                        Coordinates position = layers[i].GetRelativePosition(selection[j]);
+                        if (position.X < 0 || position.X > layers[i].Width - 1 || position.Y < 0 ||
                             position.Y > layers[i].Height - 1)
                         {
                             continue;
                         }
 
-                        pixels[j] = layers[i].GetPixel(position.X, position.Y);
-                    }
-                }
+                        pixels[j] = layers[i].GetPixel(position.X, position.Y);
+                    }
+                }
+
+                result[layers[i]] = pixels;
+            }
 
-                result[layers[i]] = pixels;
-            }
-
-            return result;
-        }
-    }
+            return result;
+        }
+    }
 }

+ 52 - 51
PixiEditor/Models/ImageManipulation/Morphology.cs

@@ -1,26 +1,26 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.ImageManipulation
-{
-    public class Morphology
-    {
-        public static IEnumerable<Coordinates> ApplyDilation(Coordinates[] points, int kernelSize, int[,] mask)
-        {
-            int kernelDim = kernelSize;
-
-            // This is the offset of center pixel from border of the kernel
-            int kernelOffset = (kernelDim - 1) / 2;
-            int margin = kernelDim;
-
-            byte[,] byteImg = GetByteArrayForPoints(points, margin);
-            byte[,] outputArray = byteImg.Clone() as byte[,];
-            Coordinates offset = new Coordinates(points.Min(x => x.X) - margin, points.Min(x => x.Y) - margin);
-
-            int width = byteImg.GetLength(0);
-            int height = byteImg.GetLength(1);
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.ImageManipulation
+{
+    public class Morphology
+    {
+        public static IEnumerable<Coordinates> ApplyDilation(Coordinates[] points, int kernelSize, int[,] mask)
+        {
+            int kernelDim = kernelSize;
+
+            // This is the offset of center pixel from border of the kernel
+            int kernelOffset = (kernelDim - 1) / 2;
+            int margin = kernelDim;
+
+            byte[,] byteImg = GetByteArrayForPoints(points, margin);
+            byte[,] outputArray = byteImg.Clone() as byte[,];
+            Coordinates offset = new Coordinates(points.Min(x => x.X) - margin, points.Min(x => x.Y) - margin);
+
+            int width = byteImg.GetLength(0);
+            int height = byteImg.GetLength(1);
             for (int y = kernelOffset; y < height - kernelOffset; y++)
             {
                 for (int x = kernelOffset; x < width - kernelOffset; x++)
@@ -42,19 +42,20 @@ namespace PixiEditor.Models.ImageManipulation
                             }
                         }
                     }
+
                     // Write processed data into the second array
                     outputArray[x, y] = value;
                 }
             }
 
-            return ToCoordinates(outputArray, offset).Distinct();
-        }
-
-        private static IEnumerable<Coordinates> ToCoordinates(byte[,] byteArray, Coordinates offset)
-        {
-            List<Coordinates> output = new List<Coordinates>();
-            int width = byteArray.GetLength(0);
-
+            return ToCoordinates(outputArray, offset).Distinct();
+        }
+
+        private static IEnumerable<Coordinates> ToCoordinates(byte[,] byteArray, Coordinates offset)
+        {
+            List<Coordinates> output = new List<Coordinates>();
+            int width = byteArray.GetLength(0);
+
             for (int y = 0; y < byteArray.GetLength(1); y++)
             {
                 for (int x = 0; x < width; x++)
@@ -66,16 +67,16 @@ namespace PixiEditor.Models.ImageManipulation
                 }
             }
 
-            return output;
-        }
-
-        private static byte[,] GetByteArrayForPoints(Coordinates[] points, int margin)
-        {
-            Tuple<int, int> dimensions = GetDimensionsForPoints(points);
-            int minX = points.Min(x => x.X);
-            int minY = points.Min(x => x.Y);
-            byte[,] array = new byte[dimensions.Item1 + margin * 2, dimensions.Item2 + margin * 2];
-
+            return output;
+        }
+
+        private static byte[,] GetByteArrayForPoints(Coordinates[] points, int margin)
+        {
+            Tuple<int, int> dimensions = GetDimensionsForPoints(points);
+            int minX = points.Min(x => x.X);
+            int minY = points.Min(x => x.Y);
+            byte[,] array = new byte[dimensions.Item1 + (margin * 2), dimensions.Item2 + (margin * 2)];
+
             for (int y = 0; y < dimensions.Item2 + margin; y++)
             {
                 for (int x = 0; x < dimensions.Item1 + margin; x++)
@@ -85,14 +86,14 @@ namespace PixiEditor.Models.ImageManipulation
                 }
             }
 
-            return array;
-        }
-
-        private static Tuple<int, int> GetDimensionsForPoints(Coordinates[] points)
-        {
-            int width = points.Max(x => x.X) - points.Min(x => x.X);
-            int height = points.Max(x => x.Y) - points.Min(x => x.Y);
-            return new Tuple<int, int>(width + 1, height + 1);
-        }
-    }
+            return array;
+        }
+
+        private static Tuple<int, int> GetDimensionsForPoints(Coordinates[] points)
+        {
+            int width = points.Max(x => x.X) - points.Min(x => x.X);
+            int height = points.Max(x => x.Y) - points.Min(x => x.Y);
+            return new Tuple<int, int>(width + 1, height + 1);
+        }
+    }
 }

+ 3 - 3
PixiEditor/Models/ImageManipulation/Transform.cs

@@ -7,9 +7,9 @@ namespace PixiEditor.Models.ImageManipulation
         /// <summary>
         ///     Returns translation between two coordinates.
         /// </summary>
-        /// <param name="from">Starting coordinate</param>
-        /// <param name="to">New coordinate</param>
-        /// <returns>Translation as coordinate</returns>
+        /// <param name="from">Starting coordinate.</param>
+        /// <param name="to">New coordinate.</param>
+        /// <returns>Translation as coordinate.</returns>
         public static Coordinates GetTranslation(Coordinates from, Coordinates to)
         {
             int translationX = to.X - from.X;

+ 415 - 422
PixiEditor/Models/Layers/Layer.cs

@@ -1,227 +1,224 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.Layers
-{
-    public class Layer : BasicLayer
-    {
-        private const int SizeOfArgb = 4;
-        private bool clipRequested;
-
-        private bool isActive;
-
-        private bool isRenaming;
-        private bool isVisible = true;
-        private WriteableBitmap layerBitmap;
-
-        private string name;
-
-        private Thickness offset;
-
-        private float opacity = 1;
-
-        public Dictionary<Coordinates, Color> LastRelativeCoordinates;
-
-        public Layer(string name)
-        {
-            Name = name;
-            LayerBitmap = BitmapFactory.New(0, 0);
-            Width = 0;
-            Height = 0;
-        }
-
-        public Layer(string name, int width, int height)
-        {
-            Name = name;
-            LayerBitmap = BitmapFactory.New(width, height);
-            Width = width;
-            Height = height;
-        }
-
-        public Layer(string name, WriteableBitmap layerBitmap)
-        {
-            Name = name;
-            LayerBitmap = layerBitmap;
-            Width = layerBitmap.PixelWidth;
-            Height = layerBitmap.PixelHeight;
-        }
-
-        public string Name
-        {
-            get => name;
-            set
-            {
-                name = value;
-                RaisePropertyChanged("Name");
-            }
-        }
-
-        public bool IsActive
-        {
-            get => isActive;
-            set
-            {
-                isActive = value;
-                RaisePropertyChanged("IsActive");
-            }
-        }
-
-        public bool IsVisible
-        {
-            get => isVisible;
-            set
-            {
-                isVisible = value;
-                RaisePropertyChanged("IsVisible");
-            }
-        }
-
-        public bool IsRenaming
-        {
-            get => isRenaming;
-            set
-            {
-                isRenaming = value;
-                RaisePropertyChanged("IsRenaming");
-            }
-        }
-
-        public WriteableBitmap LayerBitmap
-        {
-            get => layerBitmap;
-            set
-            {
-                layerBitmap = value;
-                RaisePropertyChanged("LayerBitmap");
-            }
-        }
-
-        public float Opacity
-        {
-            get => opacity;
-            set
-            {
-                opacity = value;
-                RaisePropertyChanged("Opacity");
-            }
-        }
-
-        public int OffsetX => (int)Offset.Left;
-
-        public int OffsetY => (int)Offset.Top;
-
-        public Thickness Offset
-        {
-            get => offset;
-            set
-            {
-                offset = value;
-                RaisePropertyChanged("Offset");
-            }
-        }
-
-        public int MaxWidth { get; set; } = int.MaxValue;
-
-        public int MaxHeight { get; set; } = int.MaxValue;
-
-        /// <summary>
-        ///     Returns clone of layer
-        /// </summary>
-        /// <returns></returns>
-        public Layer Clone()
-        {
-            return new Layer(Name, LayerBitmap.Clone())
-            {
-                IsVisible = IsVisible,
-                Offset = Offset,
-                MaxHeight = MaxHeight,
-                MaxWidth = MaxWidth,
-                Opacity = Opacity,
-                IsActive = IsActive,
-                IsRenaming = IsRenaming
-            };
-        }
-
-        /// <summary>
-        ///     Resizes bitmap with it's content using NearestNeighbor interpolation
-        /// </summary>
-        /// <param name="width">New width</param>
-        /// <param name="height">New height</param>
-        /// <param name="newMaxWidth">New layer maximum width, this should be document width</param>
-        /// <param name="newMaxHeight">New layer maximum height, this should be document height</param>
-        public void Resize(int width, int height, int newMaxWidth, int newMaxHeight)
-        {
-            LayerBitmap = LayerBitmap.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
-            Width = width;
-            Height = height;
-            MaxWidth = newMaxWidth;
-            MaxHeight = newMaxHeight;
-        }
-
-        /// <summary>
-        ///     Converts coordinates relative to viewport to relative to layer
-        /// </summary>
-        /// <param name="cords"></param>
-        /// <returns></returns>
-        public Coordinates GetRelativePosition(Coordinates cords)
-        {
-            return new Coordinates(cords.X - OffsetX, cords.Y - OffsetY);
-        }
-
-        /// <summary>
-        ///     Returns pixel color of x and y coordinates relative to document using (x - OffsetX) formula.
-        /// </summary>
-        /// <param name="x">Viewport relative X</param>
-        /// <param name="y">Viewport relative Y</param>
-        /// <returns>Color of a pixel</returns>
-        public Color GetPixelWithOffset(int x, int y)
-        {
-            Coordinates cords = GetRelativePosition(new Coordinates(x, y));
-            return GetPixel(cords.X, cords.Y);
-        }
-
-        /// <summary>
-        ///     Returns pixel color on x and y.
-        /// </summary>
-        /// <param name="x">X coordinate</param>
-        /// <param name="y">Y Coordinate</param>
-        /// <returns>Color of pixel, if out of bounds, returns transparent pixel.</returns>
-        public Color GetPixel(int x, int y)
-        {
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.Layers
+{
+    public class Layer : BasicLayer
+    {
+        private const int SizeOfArgb = 4;
+        private bool clipRequested;
+
+        private bool isActive;
+
+        private bool isRenaming;
+        private bool isVisible = true;
+        private WriteableBitmap layerBitmap;
+
+        private string name;
+
+        private Thickness offset;
+
+        private float opacity = 1;
+
+        public Layer(string name)
+        {
+            Name = name;
+            LayerBitmap = BitmapFactory.New(0, 0);
+            Width = 0;
+            Height = 0;
+        }
+
+        public Layer(string name, int width, int height)
+        {
+            Name = name;
+            LayerBitmap = BitmapFactory.New(width, height);
+            Width = width;
+            Height = height;
+        }
+
+        public Layer(string name, WriteableBitmap layerBitmap)
+        {
+            Name = name;
+            LayerBitmap = layerBitmap;
+            Width = layerBitmap.PixelWidth;
+            Height = layerBitmap.PixelHeight;
+        }
+
+        public Dictionary<Coordinates, Color> LastRelativeCoordinates { get; set; }
+
+        public string Name
+        {
+            get => name;
+            set
+            {
+                name = value;
+                RaisePropertyChanged("Name");
+            }
+        }
+
+        public bool IsActive
+        {
+            get => isActive;
+            set
+            {
+                isActive = value;
+                RaisePropertyChanged("IsActive");
+            }
+        }
+
+        public bool IsVisible
+        {
+            get => isVisible;
+            set
+            {
+                isVisible = value;
+                RaisePropertyChanged("IsVisible");
+            }
+        }
+
+        public bool IsRenaming
+        {
+            get => isRenaming;
+            set
+            {
+                isRenaming = value;
+                RaisePropertyChanged("IsRenaming");
+            }
+        }
+
+        public WriteableBitmap LayerBitmap
+        {
+            get => layerBitmap;
+            set
+            {
+                layerBitmap = value;
+                RaisePropertyChanged("LayerBitmap");
+            }
+        }
+
+        public float Opacity
+        {
+            get => opacity;
+            set
+            {
+                opacity = value;
+                RaisePropertyChanged("Opacity");
+            }
+        }
+
+        public int OffsetX => (int)Offset.Left;
+
+        public int OffsetY => (int)Offset.Top;
+
+        public Thickness Offset
+        {
+            get => offset;
+            set
+            {
+                offset = value;
+                RaisePropertyChanged("Offset");
+            }
+        }
+
+        public int MaxWidth { get; set; } = int.MaxValue;
+
+        public int MaxHeight { get; set; } = int.MaxValue;
+
+        /// <summary>
+        ///     Returns clone of layer.
+        /// </summary>
+        public Layer Clone()
+        {
+            return new Layer(Name, LayerBitmap.Clone())
+            {
+                IsVisible = IsVisible,
+                Offset = Offset,
+                MaxHeight = MaxHeight,
+                MaxWidth = MaxWidth,
+                Opacity = Opacity,
+                IsActive = IsActive,
+                IsRenaming = IsRenaming
+            };
+        }
+
+        /// <summary>
+        ///     Resizes bitmap with it's content using NearestNeighbor interpolation.
+        /// </summary>
+        /// <param name="width">New width.</param>
+        /// <param name="height">New height.</param>
+        /// <param name="newMaxWidth">New layer maximum width, this should be document width.</param>
+        /// <param name="newMaxHeight">New layer maximum height, this should be document height.</param>
+        public void Resize(int width, int height, int newMaxWidth, int newMaxHeight)
+        {
+            LayerBitmap = LayerBitmap.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+            Width = width;
+            Height = height;
+            MaxWidth = newMaxWidth;
+            MaxHeight = newMaxHeight;
+        }
+
+        /// <summary>
+        ///     Converts coordinates relative to viewport to relative to layer.
+        /// </summary>
+        public Coordinates GetRelativePosition(Coordinates cords)
+        {
+            return new Coordinates(cords.X - OffsetX, cords.Y - OffsetY);
+        }
+
+        /// <summary>
+        ///     Returns pixel color of x and y coordinates relative to document using (x - OffsetX) formula.
+        /// </summary>
+        /// <param name="x">Viewport relative X.</param>
+        /// <param name="y">Viewport relative Y.</param>
+        /// <returns>Color of a pixel.</returns>
+        public Color GetPixelWithOffset(int x, int y)
+        {
+            Coordinates cords = GetRelativePosition(new Coordinates(x, y));
+            return GetPixel(cords.X, cords.Y);
+        }
+
+        /// <summary>
+        ///     Returns pixel color on x and y.
+        /// </summary>
+        /// <param name="x">X coordinate.</param>
+        /// <param name="y">Y Coordinate.</param>
+        /// <returns>Color of pixel, if out of bounds, returns transparent pixel.</returns>
+        public Color GetPixel(int x, int y)
+        {
             if (x > Width - 1 || x < 0 || y > Height - 1 || y < 0)
             {
                 return Color.FromArgb(0, 0, 0, 0);
             }
 
-            return LayerBitmap.GetPixel(x, y);
-        }
-
-        /// <summary>
-        ///     Applies pixel to layer
-        /// </summary>
-        /// <param name="coordinates">Position of pixel</param>
-        /// <param name="color">Color of pixel</param>
-        /// <param name="dynamicResize">Resizes bitmap to fit content</param>
-        /// <param name="applyOffset">Converts pixels coordinates to relative to bitmap</param>
-        public void SetPixel(Coordinates coordinates, Color color, bool dynamicResize = true, bool applyOffset = true)
-        {
-            SetPixels(BitmapPixelChanges.FromSingleColoredArray(new[] { coordinates }, color), dynamicResize, applyOffset);
-        }
-
-        /// <summary>
-        ///     Applies pixels to layer
-        /// </summary>
-        /// <param name="pixels">Pixels to apply</param>
-        /// <param name="dynamicResize">Resizes bitmap to fit content</param>
-        /// <param name="applyOffset">Converts pixels coordinates to relative to bitmap</param>
-        public void SetPixels(BitmapPixelChanges pixels, bool dynamicResize = true, bool applyOffset = true)
-        {
+            return LayerBitmap.GetPixel(x, y);
+        }
+
+        /// <summary>
+        ///     Applies pixel to layer.
+        /// </summary>
+        /// <param name="coordinates">Position of pixel.</param>
+        /// <param name="color">Color of pixel.</param>
+        /// <param name="dynamicResize">Resizes bitmap to fit content.</param>
+        /// <param name="applyOffset">Converts pixels coordinates to relative to bitmap.</param>
+        public void SetPixel(Coordinates coordinates, Color color, bool dynamicResize = true, bool applyOffset = true)
+        {
+            SetPixels(BitmapPixelChanges.FromSingleColoredArray(new[] { coordinates }, color), dynamicResize, applyOffset);
+        }
+
+        /// <summary>
+        ///     Applies pixels to layer.
+        /// </summary>
+        /// <param name="pixels">Pixels to apply.</param>
+        /// <param name="dynamicResize">Resizes bitmap to fit content.</param>
+        /// <param name="applyOffset">Converts pixels coordinates to relative to bitmap.</param>
+        public void SetPixels(BitmapPixelChanges pixels, bool dynamicResize = true, bool applyOffset = true)
+        {
             if (pixels.ChangedPixels == null || pixels.ChangedPixels.Count == 0)
             {
                 return;
@@ -237,67 +234,59 @@ namespace PixiEditor.Models.Layers
                 pixels.ChangedPixels = GetRelativePosition(pixels.ChangedPixels);
             }
 
-            LastRelativeCoordinates = pixels.ChangedPixels;
-
-            using (BitmapContext ctx = LayerBitmap.GetBitmapContext())
-            {
-                foreach (KeyValuePair<Coordinates, Color> coords in pixels.ChangedPixels)
-                {
+            LastRelativeCoordinates = pixels.ChangedPixels;
+
+            using (BitmapContext ctx = LayerBitmap.GetBitmapContext())
+            {
+                foreach (KeyValuePair<Coordinates, Color> coords in pixels.ChangedPixels)
+                {
                     if (OutOfBounds(coords.Key))
                     {
                         continue;
                     }
 
-                    ctx.WriteableBitmap.SetPixel(coords.Key.X, coords.Key.Y, coords.Value);
-                }
-            }
-
-            ClipIfNecessary();
-        }
-
-        private Dictionary<Coordinates, Color> GetRelativePosition(Dictionary<Coordinates, Color> changedPixels)
-        {
-            return changedPixels.ToDictionary(d => new Coordinates(d.Key.X - OffsetX, d.Key.Y - OffsetY),
-                d => d.Value);
-        }
-
-        /// <summary>
-        ///     Converts absolute coordinates array to relative to this layer coordinates array.
-        /// </summary>
-        /// <param name="nonRelativeCords">absolute coordinates array</param>
-        /// <returns></returns>
-        public Coordinates[] ConvertToRelativeCoordinates(Coordinates[] nonRelativeCords)
-        {
-            Coordinates[] result = new Coordinates[nonRelativeCords.Length];
+                    ctx.WriteableBitmap.SetPixel(coords.Key.X, coords.Key.Y, coords.Value);
+                }
+            }
+
+            ClipIfNecessary();
+        }
+
+        /// <summary>
+        ///     Converts absolute coordinates array to relative to this layer coordinates array.
+        /// </summary>
+        /// <param name="nonRelativeCords">absolute coordinates array.</param>
+        public Coordinates[] ConvertToRelativeCoordinates(Coordinates[] nonRelativeCords)
+        {
+            Coordinates[] result = new Coordinates[nonRelativeCords.Length];
             for (int i = 0; i < nonRelativeCords.Length; i++)
             {
                 result[i] = new Coordinates(nonRelativeCords[i].X - OffsetX, nonRelativeCords[i].Y - OffsetY);
             }
 
-            return result;
-        }
-
-        /// <summary>
-        ///     Resizes canvas to fit pixels outside current bounds. Clamped to MaxHeight and MaxWidth
-        /// </summary>
-        /// <param name="pixels"></param>
-        public void DynamicResize(BitmapPixelChanges pixels)
-        {
+            return result;
+        }
+
+        /// <summary>
+        ///     Resizes canvas to fit pixels outside current bounds. Clamped to MaxHeight and MaxWidth.
+        /// </summary>
+        public void DynamicResize(BitmapPixelChanges pixels)
+        {
             if (pixels.ChangedPixels.Count == 0)
             {
                 return;
             }
 
-            ResetOffset(pixels);
-            Tuple<DoubleCords, bool> borderData = ExtractBorderData(pixels);
-            DoubleCords minMaxCords = borderData.Item1;
-            int newMaxX = minMaxCords.Coords2.X - OffsetX;
-            int newMaxY = minMaxCords.Coords2.Y - OffsetY;
-            int newMinX = minMaxCords.Coords1.X - OffsetX;
-            int newMinY = minMaxCords.Coords1.Y - OffsetY;
-
-            if (!(pixels.WasBuiltAsSingleColored && pixels.ChangedPixels.First().Value.A == 0))
-            {
+            ResetOffset(pixels);
+            Tuple<DoubleCords, bool> borderData = ExtractBorderData(pixels);
+            DoubleCords minMaxCords = borderData.Item1;
+            int newMaxX = minMaxCords.Coords2.X - OffsetX;
+            int newMaxY = minMaxCords.Coords2.Y - OffsetY;
+            int newMinX = minMaxCords.Coords1.X - OffsetX;
+            int newMinY = minMaxCords.Coords1.Y - OffsetY;
+
+            if (!(pixels.WasBuiltAsSingleColored && pixels.ChangedPixels.First().Value.A == 0))
+            {
                 if (newMaxX + 1 > Width || newMaxY + 1 > Height)
                 {
                     IncreaseSizeToBottom(newMaxX, newMaxY);
@@ -307,25 +296,74 @@ namespace PixiEditor.Models.Layers
                 {
                     IncreaseSizeToTop(newMinX, newMinY);
                 }
-            }
-
-            if (borderData.Item2) // if clip is requested
+            }
+
+            // if clip is requested
+            if (borderData.Item2)
             {
                 clipRequested = true;
             }
-        }
-
-        private Tuple<DoubleCords, bool> ExtractBorderData(BitmapPixelChanges pixels)
-        {
-            Coordinates firstCords = pixels.ChangedPixels.First().Key;
-            int minX = firstCords.X;
-            int minY = firstCords.Y;
-            int maxX = minX;
-            int maxY = minY;
-            bool clipRequested = false;
-
-            foreach (KeyValuePair<Coordinates, Color> pixel in pixels.ChangedPixels)
-            {
+        }
+
+        /// <summary>
+        ///     Changes size of bitmap to fit content.
+        /// </summary>
+        public void ClipCanvas()
+        {
+            DoubleCords points = GetEdgePoints();
+            int smallestX = points.Coords1.X;
+            int smallestY = points.Coords1.Y;
+            int biggestX = points.Coords2.X;
+            int biggestY = points.Coords2.Y;
+
+            if (smallestX < 0 && smallestY < 0 && biggestX < 0 && biggestY < 0)
+            {
+                return;
+            }
+
+            int width = biggestX - smallestX + 1;
+            int height = biggestY - smallestY + 1;
+            ResizeCanvas(0, 0, smallestX, smallestY, width, height);
+            Offset = new Thickness(OffsetX + smallestX, OffsetY + smallestY, 0, 0);
+        }
+
+        /// <summary>
+        ///     Clears bitmap.
+        /// </summary>
+        public void Clear()
+        {
+            LayerBitmap.Clear();
+        }
+
+        /// <summary>
+        ///     Converts layer WriteableBitmap to byte array.
+        /// </summary>
+        public byte[] ConvertBitmapToBytes()
+        {
+            LayerBitmap.Lock();
+            byte[] byteArray = LayerBitmap.ToByteArray();
+            LayerBitmap.Unlock();
+            return byteArray;
+        }
+
+        private Dictionary<Coordinates, Color> GetRelativePosition(Dictionary<Coordinates, Color> changedPixels)
+        {
+            return changedPixels.ToDictionary(
+                d => new Coordinates(d.Key.X - OffsetX, d.Key.Y - OffsetY),
+                d => d.Value);
+        }
+
+        private Tuple<DoubleCords, bool> ExtractBorderData(BitmapPixelChanges pixels)
+        {
+            Coordinates firstCords = pixels.ChangedPixels.First().Key;
+            int minX = firstCords.X;
+            int minY = firstCords.Y;
+            int maxX = minX;
+            int maxY = minY;
+            bool clipRequested = false;
+
+            foreach (KeyValuePair<Coordinates, Color> pixel in pixels.ChangedPixels)
+            {
                 if (pixel.Key.X < minX)
                 {
                     minX = pixel.Key.X;
@@ -348,153 +386,108 @@ namespace PixiEditor.Models.Layers
                 {
                     clipRequested = true;
                 }
-            }
-
-            return new Tuple<DoubleCords, bool>(
-                new DoubleCords(new Coordinates(minX, minY), new Coordinates(maxX, maxY)), clipRequested);
-        }
-
-        private bool IsBorderPixel(Coordinates cords)
-        {
-            return cords.X - OffsetX == 0 || cords.Y - OffsetY == 0 || cords.X - OffsetX == Width - 1 ||
-                   cords.Y - OffsetY == Height - 1;
-        }
-
-        private bool OutOfBounds(Coordinates cords)
-        {
-            return cords.X < 0 || cords.X > Width - 1 || cords.Y < 0 || cords.Y > Height - 1;
-        }
-
-        private void ClipIfNecessary()
-        {
-            if (clipRequested)
-            {
-                ClipCanvas();
-                clipRequested = false;
-            }
-        }
-
-        /// <summary>
-        ///     Changes size of bitmap to fit content
-        /// </summary>
-        public void ClipCanvas()
-        {
-            DoubleCords points = GetEdgePoints();
-            int smallestX = points.Coords1.X;
-            int smallestY = points.Coords1.Y;
-            int biggestX = points.Coords2.X;
-            int biggestY = points.Coords2.Y;
-
-            if (smallestX < 0 && smallestY < 0 && biggestX < 0 && biggestY < 0)
+            }
+
+            return new Tuple<DoubleCords, bool>(
+                new DoubleCords(new Coordinates(minX, minY), new Coordinates(maxX, maxY)), clipRequested);
+        }
+
+        private bool IsBorderPixel(Coordinates cords)
+        {
+            return cords.X - OffsetX == 0 || cords.Y - OffsetY == 0 || cords.X - OffsetX == Width - 1 ||
+                   cords.Y - OffsetY == Height - 1;
+        }
+
+        private bool OutOfBounds(Coordinates cords)
+        {
+            return cords.X < 0 || cords.X > Width - 1 || cords.Y < 0 || cords.Y > Height - 1;
+        }
+
+        private void ClipIfNecessary()
+        {
+            if (clipRequested)
             {
-                return;
+                ClipCanvas();
+                clipRequested = false;
             }
+        }
 
-            int width = biggestX - smallestX + 1;
-            int height = biggestY - smallestY + 1;
-            ResizeCanvas(0, 0, smallestX, smallestY, width, height);
-            Offset = new Thickness(OffsetX + smallestX, OffsetY + smallestY, 0, 0);
-        }
-
-        private void IncreaseSizeToBottom(int newMaxX, int newMaxY)
-        {
+        private void IncreaseSizeToBottom(int newMaxX, int newMaxY)
+        {
             if (MaxWidth - OffsetX < 0 || MaxHeight - OffsetY < 0)
             {
                 return;
             }
 
-            newMaxX = Math.Clamp(Math.Max(newMaxX + 1, Width), 0, MaxWidth - OffsetX);
-            newMaxY = Math.Clamp(Math.Max(newMaxY + 1, Height), 0, MaxHeight - OffsetY);
-
-            ResizeCanvas(0, 0, 0, 0, newMaxX, newMaxY);
-        }
-
-        private void IncreaseSizeToTop(int newMinX, int newMinY)
-        {
-            newMinX = Math.Clamp(Math.Min(newMinX, Width), Math.Min(-OffsetX, OffsetX), 0);
-            newMinY = Math.Clamp(Math.Min(newMinY, Height), Math.Min(-OffsetY, OffsetY), 0);
-
-            Offset = new Thickness(Math.Clamp(OffsetX + newMinX, 0, MaxWidth),
-                Math.Clamp(OffsetY + newMinY, 0, MaxHeight), 0, 0);
-
-            int newWidth = Math.Clamp(Width - newMinX, 0, MaxWidth);
-            int newHeight = Math.Clamp(Height - newMinY, 0, MaxHeight);
-
-            int offsetX = Math.Abs(newWidth - Width);
-            int offsetY = Math.Abs(newHeight - Height);
-
-            ResizeCanvas(offsetX, offsetY, 0, 0, newWidth, newHeight);
-        }
-
-        private DoubleCords GetEdgePoints()
-        {
-            Coordinates smallestPixel = CoordinatesCalculator.FindMinEdgeNonTransparentPixel(LayerBitmap);
-            Coordinates biggestPixel = CoordinatesCalculator.FindMostEdgeNonTransparentPixel(LayerBitmap);
-
-            return new DoubleCords(smallestPixel, biggestPixel);
-        }
-
-        private void ResetOffset(BitmapPixelChanges pixels)
-        {
-            if (Width == 0 || Height == 0)
-            {
-                int offsetX = pixels.ChangedPixels.Min(x => x.Key.X);
-                int offsetY = pixels.ChangedPixels.Min(x => x.Key.Y);
-                Offset = new Thickness(offsetX, offsetY, 0, 0);
-            }
-        }
-
-        /// <summary>
-        ///     Clears bitmap
-        /// </summary>
-        public void Clear()
-        {
-            LayerBitmap.Clear();
-        }
-
-        /// <summary>
-        ///     Converts layer WriteableBitmap to byte array
-        /// </summary>
-        /// <returns></returns>
-        public byte[] ConvertBitmapToBytes()
-        {
-            LayerBitmap.Lock();
-            byte[] byteArray = LayerBitmap.ToByteArray();
-            LayerBitmap.Unlock();
-            return byteArray;
-        }
-
-        /// <summary>
-        ///     Resizes canvas to new size with specified offset.
-        /// </summary>
-        /// <param name="offsetX"></param>
-        /// <param name="offsetY"></param>
-        /// <param name="offsetXSrc"></param>
-        /// <param name="offsetYSrc"></param>
-        /// <param name="newWidth"></param>
-        /// <param name="newHeight"></param>
-        private void ResizeCanvas(int offsetX, int offsetY, int offsetXSrc, int offsetYSrc, int newWidth, int newHeight)
-        {
-            int iteratorHeight = Height > newHeight ? newHeight : Height;
-            int count = Width > newWidth ? newWidth : Width;
-
-            using (BitmapContext srcContext = LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
-            {
-                WriteableBitmap result = BitmapFactory.New(newWidth, newHeight);
-                using (BitmapContext destContext = result.GetBitmapContext())
-                {
-                    for (int line = 0; line < iteratorHeight; line++)
-                    {
-                        int srcOff = ((offsetYSrc + line) * Width + offsetXSrc) * SizeOfArgb;
-                        int dstOff = ((offsetY + line) * newWidth + offsetX) * SizeOfArgb;
-                        BitmapContext.BlockCopy(srcContext, srcOff, destContext, dstOff, count * SizeOfArgb);
-                    }
-
-                    LayerBitmap = result;
-                    Width = newWidth;
-                    Height = newHeight;
-                }
-            }
-        }
-    }
+            newMaxX = Math.Clamp(Math.Max(newMaxX + 1, Width), 0, MaxWidth - OffsetX);
+            newMaxY = Math.Clamp(Math.Max(newMaxY + 1, Height), 0, MaxHeight - OffsetY);
+
+            ResizeCanvas(0, 0, 0, 0, newMaxX, newMaxY);
+        }
+
+        private void IncreaseSizeToTop(int newMinX, int newMinY)
+        {
+            newMinX = Math.Clamp(Math.Min(newMinX, Width), Math.Min(-OffsetX, OffsetX), 0);
+            newMinY = Math.Clamp(Math.Min(newMinY, Height), Math.Min(-OffsetY, OffsetY), 0);
+
+            Offset = new Thickness(
+                Math.Clamp(OffsetX + newMinX, 0, MaxWidth),
+                Math.Clamp(OffsetY + newMinY, 0, MaxHeight),
+                0,
+                0);
+
+            int newWidth = Math.Clamp(Width - newMinX, 0, MaxWidth);
+            int newHeight = Math.Clamp(Height - newMinY, 0, MaxHeight);
+
+            int offsetX = Math.Abs(newWidth - Width);
+            int offsetY = Math.Abs(newHeight - Height);
+
+            ResizeCanvas(offsetX, offsetY, 0, 0, newWidth, newHeight);
+        }
+
+        private DoubleCords GetEdgePoints()
+        {
+            Coordinates smallestPixel = CoordinatesCalculator.FindMinEdgeNonTransparentPixel(LayerBitmap);
+            Coordinates biggestPixel = CoordinatesCalculator.FindMostEdgeNonTransparentPixel(LayerBitmap);
+
+            return new DoubleCords(smallestPixel, biggestPixel);
+        }
+
+        private void ResetOffset(BitmapPixelChanges pixels)
+        {
+            if (Width == 0 || Height == 0)
+            {
+                int offsetX = pixels.ChangedPixels.Min(x => x.Key.X);
+                int offsetY = pixels.ChangedPixels.Min(x => x.Key.Y);
+                Offset = new Thickness(offsetX, offsetY, 0, 0);
+            }
+        }
+
+        /// <summary>
+        ///     Resizes canvas to new size with specified offset.
+        /// </summary>
+        private void ResizeCanvas(int offsetX, int offsetY, int offsetXSrc, int offsetYSrc, int newWidth, int newHeight)
+        {
+            int iteratorHeight = Height > newHeight ? newHeight : Height;
+            int count = Width > newWidth ? newWidth : Width;
+
+            using (BitmapContext srcContext = LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
+            {
+                WriteableBitmap result = BitmapFactory.New(newWidth, newHeight);
+                using (BitmapContext destContext = result.GetBitmapContext())
+                {
+                    for (int line = 0; line < iteratorHeight; line++)
+                    {
+                        int srcOff = (((offsetYSrc + line) * Width) + offsetXSrc) * SizeOfArgb;
+                        int dstOff = (((offsetY + line) * newWidth) + offsetX) * SizeOfArgb;
+                        BitmapContext.BlockCopy(srcContext, srcOff, destContext, dstOff, count * SizeOfArgb);
+                    }
+
+                    LayerBitmap = result;
+                    Width = newWidth;
+                    Height = newHeight;
+                }
+            }
+        }
+    }
 }

+ 71 - 71
PixiEditor/Models/Layers/SerializableLayer.cs

@@ -1,77 +1,77 @@
-using System;
-using System.Linq;
-
-namespace PixiEditor.Models.Layers
-{
-    [Serializable]
-    public class SerializableLayer
-    {
-        public SerializableLayer(Layer layer)
-        {
-            Name = layer.Name;
-            Width = layer.Width;
-            Height = layer.Height;
-            BitmapBytes = layer.ConvertBitmapToBytes();
-            IsVisible = layer.IsVisible;
-            OffsetX = (int)layer.Offset.Left;
-            OffsetY = (int)layer.Offset.Top;
-            Opacity = layer.Opacity;
-            MaxWidth = layer.MaxWidth;
-            MaxHeight = layer.MaxHeight;
-        }
-
-        public string Name { get; set; }
-
-        public int Width { get; set; }
-
-        public int Height { get; set; }
-
-        public int MaxWidth { get; set; }
-
-        public int MaxHeight { get; set; }
-
-        public byte[] BitmapBytes { get; set; }
-
-        public bool IsVisible { get; set; }
-
-        public int OffsetX { get; set; }
-
-        public int OffsetY { get; set; }
-
-        public float Opacity { get; set; }
-
-        public override bool Equals(object? obj)
-        {
+using System;
+using System.Linq;
+
+namespace PixiEditor.Models.Layers
+{
+    [Serializable]
+    public class SerializableLayer
+    {
+        public SerializableLayer(Layer layer)
+        {
+            Name = layer.Name;
+            Width = layer.Width;
+            Height = layer.Height;
+            BitmapBytes = layer.ConvertBitmapToBytes();
+            IsVisible = layer.IsVisible;
+            OffsetX = (int)layer.Offset.Left;
+            OffsetY = (int)layer.Offset.Top;
+            Opacity = layer.Opacity;
+            MaxWidth = layer.MaxWidth;
+            MaxHeight = layer.MaxHeight;
+        }
+
+        public string Name { get; set; }
+
+        public int Width { get; set; }
+
+        public int Height { get; set; }
+
+        public int MaxWidth { get; set; }
+
+        public int MaxHeight { get; set; }
+
+        public byte[] BitmapBytes { get; set; }
+
+        public bool IsVisible { get; set; }
+
+        public int OffsetX { get; set; }
+
+        public int OffsetY { get; set; }
+
+        public float Opacity { get; set; }
+
+        public override bool Equals(object obj)
+        {
             if (obj == null || obj.GetType() != typeof(SerializableLayer))
             {
                 return false;
             }
 
-            SerializableLayer layer = (SerializableLayer)obj;
-
-            return Equals(layer);
-        }
-
-        protected bool Equals(SerializableLayer other)
-        {
-            return Name == other.Name && Width == other.Width && Height == other.Height && MaxWidth == other.MaxWidth && MaxHeight == other.MaxHeight &&
-                   BitmapBytes.SequenceEqual(other.BitmapBytes) && IsVisible == other.IsVisible && OffsetX == other.OffsetX && OffsetY == other.OffsetY && Opacity.Equals(other.Opacity);
-        }
-
-        public override int GetHashCode()
-        {
-            HashCode hashCode = new HashCode();
-            hashCode.Add(Name);
-            hashCode.Add(Width);
-            hashCode.Add(Height);
-            hashCode.Add(MaxWidth);
-            hashCode.Add(MaxHeight);
-            hashCode.Add(BitmapBytes);
-            hashCode.Add(IsVisible);
-            hashCode.Add(OffsetX);
-            hashCode.Add(OffsetY);
-            hashCode.Add(Opacity);
-            return hashCode.ToHashCode();
-        }
-    }
+            SerializableLayer layer = (SerializableLayer)obj;
+
+            return Equals(layer);
+        }
+
+        public override int GetHashCode()
+        {
+            HashCode hashCode = default(HashCode);
+            hashCode.Add(Name);
+            hashCode.Add(Width);
+            hashCode.Add(Height);
+            hashCode.Add(MaxWidth);
+            hashCode.Add(MaxHeight);
+            hashCode.Add(BitmapBytes);
+            hashCode.Add(IsVisible);
+            hashCode.Add(OffsetX);
+            hashCode.Add(OffsetY);
+            hashCode.Add(Opacity);
+            return hashCode.ToHashCode();
+        }
+
+        protected bool Equals(SerializableLayer other)
+        {
+            return Name == other.Name && Width == other.Width && Height == other.Height && MaxWidth == other.MaxWidth && MaxHeight == other.MaxHeight &&
+                   BitmapBytes.SequenceEqual(other.BitmapBytes) && IsVisible == other.IsVisible && OffsetX == other.OffsetX && OffsetY == other.OffsetY && Opacity.Equals(other.Opacity);
+        }
+    }
 }

+ 8 - 8
PixiEditor/Models/Position/Coordinates.cs

@@ -4,20 +4,15 @@ namespace PixiEditor.Models.Position
 {
     public struct Coordinates
     {
-        public int X { get; set; }
-
-        public int Y { get; set; }
-
         public Coordinates(int x, int y)
         {
             X = x;
             Y = y;
         }
 
-        public override string ToString()
-        {
-            return $"{X}, {Y}";
-        }
+        public int X { get; set; }
+
+        public int Y { get; set; }
 
         public static bool operator ==(Coordinates c1, Coordinates c2)
         {
@@ -29,6 +24,11 @@ namespace PixiEditor.Models.Position
             return !(c1 == c2);
         }
 
+        public override string ToString()
+        {
+            return $"{X}, {Y}";
+        }
+
         public override bool Equals(object obj)
         {
             if (obj is Coordinates coords)

+ 15 - 22
PixiEditor/Models/Position/CoordinatesCalculator.cs

@@ -8,25 +8,24 @@ namespace PixiEditor.Models.Position
     public static class CoordinatesCalculator
     {
         /// <summary>
-        ///     Calculates center of thickness * thickness rectangle
+        ///     Calculates center of thickness * thickness rectangle.
         /// </summary>
-        /// <param name="startPosition">Top left position of rectangle</param>
-        /// <param name="thickness">Thickness of rectangle</param>
-        /// <returns></returns>
+        /// <param name="startPosition">Top left position of rectangle.</param>
+        /// <param name="thickness">Thickness of rectangle.</param>
         public static DoubleCords CalculateThicknessCenter(Coordinates startPosition, int thickness)
         {
             int x1, x2, y1, y2;
             if (thickness % 2 == 0)
             {
-                x2 = startPosition.X + thickness / 2;
-                y2 = startPosition.Y + thickness / 2;
+                x2 = startPosition.X + (thickness / 2);
+                y2 = startPosition.Y + (thickness / 2);
                 x1 = x2 - thickness;
                 y1 = y2 - thickness;
             }
             else
             {
-                x2 = startPosition.X + (thickness - 1) / 2 + 1;
-                y2 = startPosition.Y + (thickness - 1) / 2 + 1;
+                x2 = startPosition.X + ((thickness - 1) / 2) + 1;
+                y2 = startPosition.Y + ((thickness - 1) / 2) + 1;
                 x1 = x2 - thickness;
                 y1 = y2 - thickness;
             }
@@ -42,13 +41,12 @@ namespace PixiEditor.Models.Position
         }
 
         /// <summary>
-        ///     Calculates coordinates of rectangle by edge points x1, y1, x2, y2
+        ///     Calculates coordinates of rectangle by edge points x1, y1, x2, y2.
         /// </summary>
-        /// <param name="x1">Top left x point</param>
-        /// <param name="y1">Top left y position</param>
-        /// <param name="x2">Bottom right x position</param>
-        /// <param name="y2">Bottom right Y position</param>
-        /// <returns></returns>
+        /// <param name="x1">Top left x point.</param>
+        /// <param name="y1">Top left y position.</param>
+        /// <param name="x2">Bottom right x position.</param>
+        /// <param name="y2">Bottom right Y position.</param>
         public static Coordinates[] RectangleToCoordinates(int x1, int y1, int x2, int y2)
         {
             x2++;
@@ -67,25 +65,20 @@ namespace PixiEditor.Models.Position
 
         public static Coordinates[] RectangleToCoordinates(DoubleCords coordinates)
         {
-            return RectangleToCoordinates(coordinates.Coords1.X, coordinates.Coords1.Y, coordinates.Coords2.X,
-                coordinates.Coords2.Y);
+            return RectangleToCoordinates(coordinates.Coords1.X, coordinates.Coords1.Y, coordinates.Coords2.X, coordinates.Coords2.Y);
         }
 
         /// <summary>
-        ///     Returns first pixel coordinates in bitmap that is most top left on canvas
+        ///     Returns first pixel coordinates in bitmap that is most top left on canvas.
         /// </summary>
-        /// <param name="bitmap"></param>
-        /// <returns></returns>
         public static Coordinates FindMinEdgeNonTransparentPixel(WriteableBitmap bitmap)
         {
             return new Coordinates(FindMinXNonTransparent(bitmap), FindMinYNonTransparent(bitmap));
         }
 
         /// <summary>
-        ///     Returns last pixel coordinates that is most bottom right
+        ///     Returns last pixel coordinates that is most bottom right.
         /// </summary>
-        /// <param name="bitmap"></param>
-        /// <returns></returns>
         public static Coordinates FindMostEdgeNonTransparentPixel(WriteableBitmap bitmap)
         {
             return new Coordinates(FindMaxXNonTransparent(bitmap), FindMaxYNonTransparent(bitmap));

+ 13 - 13
PixiEditor/Models/Position/DoubleCords.cs

@@ -1,15 +1,15 @@
-namespace PixiEditor.Models.Position
-{
-    public struct DoubleCords
-    {
-        public Coordinates Coords1 { get; set; }
+namespace PixiEditor.Models.Position
+{
+    public struct DoubleCords
+    {
+        public DoubleCords(Coordinates cords1, Coordinates cords2)
+        {
+            Coords1 = cords1;
+            Coords2 = cords2;
+        }
 
-        public Coordinates Coords2 { get; set; }
-
-        public DoubleCords(Coordinates cords1, Coordinates cords2)
-        {
-            Coords1 = cords1;
-            Coords2 = cords2;
-        }
-    }
+        public Coordinates Coords1 { get; set; }
+
+        public Coordinates Coords2 { get; set; }
+    }
 }

+ 5 - 5
PixiEditor/Models/Position/MousePositionConverter.cs

@@ -5,15 +5,15 @@ namespace PixiEditor.Models.Position
 {
     public static class MousePositionConverter
     {
-        public static Coordinates CurrentCoordinates { get; set; }
-
-        [DllImport("user32.dll")]
-        private static extern bool GetCursorPos(out Point point);
-
+        public static Coordinates CurrentCoordinates { get; set; }
+
         public static Point GetCursorPosition()
         {
             GetCursorPos(out Point point);
             return point;
         }
+
+        [DllImport("user32.dll")]
+        private static extern bool GetCursorPos(out Point point);
     }
 }

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

@@ -35,32 +35,37 @@ namespace PixiEditor.Models.Tools
             return output.Distinct();
         }
 
-        protected DoubleCords CalculateCoordinatesForShapeRotation(Coordinates startingCords,
+        protected DoubleCords CalculateCoordinatesForShapeRotation(
+            Coordinates startingCords,
             Coordinates secondCoordinates)
         {
             Coordinates currentCoordinates = secondCoordinates;
 
             if (startingCords.X > currentCoordinates.X && startingCords.Y > currentCoordinates.Y)
             {
-                return new DoubleCords(new Coordinates(currentCoordinates.X, currentCoordinates.Y),
+                return new DoubleCords(
+                    new Coordinates(currentCoordinates.X, currentCoordinates.Y),
                     new Coordinates(startingCords.X, startingCords.Y));
             }
 
             if (startingCords.X < currentCoordinates.X && startingCords.Y < currentCoordinates.Y)
             {
-                return new DoubleCords(new Coordinates(startingCords.X, startingCords.Y),
+                return new DoubleCords(
+                    new Coordinates(startingCords.X, startingCords.Y),
                     new Coordinates(currentCoordinates.X, currentCoordinates.Y));
             }
 
             if (startingCords.Y > currentCoordinates.Y)
             {
-                return new DoubleCords(new Coordinates(startingCords.X, currentCoordinates.Y),
+                return new DoubleCords(
+                    new Coordinates(startingCords.X, currentCoordinates.Y),
                     new Coordinates(currentCoordinates.X, startingCords.Y));
             }
 
             if (startingCords.X > currentCoordinates.X && startingCords.Y <= currentCoordinates.Y)
             {
-                return new DoubleCords(new Coordinates(currentCoordinates.X, startingCords.Y),
+                return new DoubleCords(
+                    new Coordinates(currentCoordinates.X, startingCords.Y),
                     new Coordinates(startingCords.X, currentCoordinates.Y));
             }
 

+ 42 - 38
PixiEditor/Models/Tools/ToolSettings/Settings/FloatSetting.cs

@@ -1,40 +1,44 @@
-using System.Windows.Data;
-using PixiEditor.Views;
-
-namespace PixiEditor.Models.Tools.ToolSettings.Settings
-{
-    public class FloatSetting : Setting<float>
-    {
-        public FloatSetting(string name, float initialValue, string label = "",
-            float min = float.NegativeInfinity, float max = float.PositiveInfinity)
-            : base(name)
-        {
-            Label = label;
-            Value = initialValue;
-            Min = min;
-            Max = max;
-            SettingControl = GenerateNumberInput();
-        }
-
-        public float Min { get; set; }
+using System.Windows.Data;
+using PixiEditor.Views;
 
-        public float Max { get; set; }
-
-        private NumberInput GenerateNumberInput()
-        {
-            NumberInput numbrInput = new NumberInput
-            {
-                Width = 40,
-                Height = 20,
-                Min = Min,
-                Max = Max
-            };
-            Binding binding = new Binding("Value")
-            {
-                Mode = BindingMode.TwoWay
-            };
-            numbrInput.SetBinding(NumberInput.ValueProperty, binding);
-            return numbrInput;
-        }
-    }
+namespace PixiEditor.Models.Tools.ToolSettings.Settings
+{
+    public class FloatSetting : Setting<float>
+    {
+        public FloatSetting(
+            string name,
+            float initialValue,
+            string label = "",
+            float min = float.NegativeInfinity,
+            float max = float.PositiveInfinity)
+            : base(name)
+        {
+            Label = label;
+            Value = initialValue;
+            Min = min;
+            Max = max;
+            SettingControl = GenerateNumberInput();
+        }
+
+        public float Min { get; set; }
+
+        public float Max { get; set; }
+
+        private NumberInput GenerateNumberInput()
+        {
+            NumberInput numbrInput = new NumberInput
+            {
+                Width = 40,
+                Height = 20,
+                Min = Min,
+                Max = Max
+            };
+            Binding binding = new Binding("Value")
+            {
+                Mode = BindingMode.TwoWay
+            };
+            numbrInput.SetBinding(NumberInput.ValueProperty, binding);
+            return numbrInput;
+        }
+    }
 }

+ 0 - 20
PixiEditor/Models/Tools/ToolSettings/Settings/Setting.cs

@@ -3,26 +3,6 @@ using PixiEditor.Helpers;
 
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
-    public abstract class Setting<T> : Setting
-    {
-        private T value;
-
-        protected Setting(string name)
-            : base(name)
-        {
-        }
-
-        public T Value
-        {
-            get => value;
-            set
-            {
-                this.value = value;
-                RaisePropertyChanged("Value");
-            }
-        }
-    }
-
     public abstract class Setting : NotifyableObject
     {
         protected Setting(string name)

+ 22 - 0
PixiEditor/Models/Tools/ToolSettings/Settings/Setting{T}.cs

@@ -0,0 +1,22 @@
+namespace PixiEditor.Models.Tools.ToolSettings.Settings
+{
+    public abstract class Setting<T> : Setting
+    {
+        private T value;
+
+        protected Setting(string name)
+            : base(name)
+        {
+        }
+
+        public T Value
+        {
+            get => value;
+            set
+            {
+                this.value = value;
+                RaisePropertyChanged("Value");
+            }
+        }
+    }
+}

+ 1 - 1
PixiEditor/Models/Tools/ToolSettings/Toolbars/BasicToolbar.cs

@@ -3,7 +3,7 @@
 namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
 {
     /// <summary>
-    ///     Toolbar with size setting
+    ///     Toolbar with size setting.
     /// </summary>
     public class BasicToolbar : Toolbar
     {

+ 2 - 2
PixiEditor/Models/Tools/ToolSettings/Toolbars/Toolbar.cs

@@ -15,7 +15,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
         /// <summary>
         ///     Gets setting in toolbar by name.
         /// </summary>
-        /// <param name="name">Setting name, non case sensitive</param>
+        /// <param name="name">Setting name, non case sensitive.</param>
         /// <returns></returns>
         public virtual Setting GetSetting(string name)
         {
@@ -25,7 +25,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
         /// <summary>
         ///     Gets setting of given type T in toolbar by name.
         /// </summary>
-        /// <param name="name">Setting name, non case sensitive</param>
+        /// <param name="name">Setting name, non case sensitive.</param>
         /// <returns></returns>
         public T GetSetting<T>(string name)
             where T : Setting

+ 79 - 74
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -1,90 +1,95 @@
-using System;
-using System.Collections.Generic;
-using System.Windows.Controls;
-using System.Windows.Input;
-using System.Windows.Media;
-using PixiEditor.Models.Colors;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class BrightnessTool : BitmapOperationTool
-    {
-        private const float CorrectionFactor = 5f; // Initial correction factor
-
-        private readonly List<Coordinates> pixelsVisited = new List<Coordinates>();
-
-        public BrightnessTool()
-        {
-            Tooltip = "Makes pixel brighter or darker pixel (U). Hold Ctrl to make pixel darker.";
-            Toolbar = new BrightnessToolToolbar(CorrectionFactor);
-        }
-
-        public override ToolType ToolType => ToolType.Brightness;
+using System;
+using System.Collections.Generic;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using PixiEditor.Models.Colors;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 
-        public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
-
-        public override void OnMouseDown(MouseEventArgs e)
-        {
-            pixelsVisited.Clear();
-        }
-
-        public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
-        {
-            int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
-            float correctionFactor = Toolbar.GetSetting<FloatSetting>("CorrectionFactor").Value;
-            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out BrightnessMode mode);
-            Mode = mode;
-
-            LayerChange[] layersChanges = new LayerChange[1];
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class BrightnessTool : BitmapOperationTool
+    {
+        private const float CorrectionFactor = 5f; // Initial correction factor
+
+        private readonly List<Coordinates> pixelsVisited = new List<Coordinates>();
+
+        public BrightnessTool()
+        {
+            Tooltip = "Makes pixel brighter or darker pixel (U). Hold Ctrl to make pixel darker.";
+            Toolbar = new BrightnessToolToolbar(CorrectionFactor);
+        }
+
+        public override ToolType ToolType => ToolType.Brightness;
+
+        public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
+
+        public override void OnMouseDown(MouseEventArgs e)
+        {
+            pixelsVisited.Clear();
+        }
+
+        public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
+        {
+            int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
+            float correctionFactor = Toolbar.GetSetting<FloatSetting>("CorrectionFactor").Value;
+            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out BrightnessMode mode);
+            Mode = mode;
+
+            LayerChange[] layersChanges = new LayerChange[1];
             if (Keyboard.IsKeyDown(Key.LeftCtrl))
             {
-                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor),
+                layersChanges[0] = new LayerChange(
+                    ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor),
                     layer);
             }
             else
             {
-                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor),
+                layersChanges[0] = new LayerChange(
+                    ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor),
                     layer);
             }
 
-            return layersChanges;
-        }
-
-        public BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize,
-            float correctionFactor)
-        {
-            DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
-            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(centeredCoords.Coords1.X,
-                centeredCoords.Coords1.Y,
-                centeredCoords.Coords2.X, centeredCoords.Coords2.Y);
-            BitmapPixelChanges changes = new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
-
-            for (int i = 0; i < rectangleCoordinates.Length; i++)
-            {
-                if (Mode == BrightnessMode.Default)
-                {
+            return layersChanges;
+        }
+
+        public BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
+        {
+            DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
+            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(
+                centeredCoords.Coords1.X,
+                centeredCoords.Coords1.Y,
+                centeredCoords.Coords2.X,
+                centeredCoords.Coords2.Y);
+            BitmapPixelChanges changes = new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
+
+            for (int i = 0; i < rectangleCoordinates.Length; i++)
+            {
+                if (Mode == BrightnessMode.Default)
+                {
                     if (pixelsVisited.Contains(rectangleCoordinates[i]))
                     {
                         continue;
                     }
 
-                    pixelsVisited.Add(rectangleCoordinates[i]);
-                }
-
-                Color pixel = layer.GetPixelWithOffset(rectangleCoordinates[i].X, rectangleCoordinates[i].Y);
-                Color newColor = ExColor.ChangeColorBrightness(Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B),
-                    correctionFactor);
-                changes.ChangedPixels.Add(new Coordinates(rectangleCoordinates[i].X, rectangleCoordinates[i].Y),
-                    newColor);
-            }
-
-            return changes;
-        }
-    }
+                    pixelsVisited.Add(rectangleCoordinates[i]);
+                }
+
+                Color pixel = layer.GetPixelWithOffset(rectangleCoordinates[i].X, rectangleCoordinates[i].Y);
+                Color newColor = ExColor.ChangeColorBrightness(
+                    Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B),
+                    correctionFactor);
+                changes.ChangedPixels.Add(
+                    new Coordinates(rectangleCoordinates[i].X, rectangleCoordinates[i].Y),
+                    newColor);
+            }
+
+            return changes;
+        }
+    }
 }

+ 18 - 18
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -39,13 +39,12 @@ namespace PixiEditor.Models.Tools.Tools
         /// <summary>
         ///     Calculates ellipse points for specified coordinates and thickness.
         /// </summary>
-        /// <param name="startCoordinates">Top left coordinate of ellipse</param>
-        /// <param name="endCoordinates">Bottom right coordinate of ellipse</param>
-        /// <param name="thickness">Thickness of ellipse</param>
-        /// <param name="filled">Should ellipse be filled</param>
-        /// <returns>Coordinates for ellipse</returns>
-        public IEnumerable<Coordinates> CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness,
-            bool filled)
+        /// <param name="startCoordinates">Top left coordinate of ellipse.</param>
+        /// <param name="endCoordinates">Bottom right coordinate of ellipse.</param>
+        /// <param name="thickness">Thickness of ellipse.</param>
+        /// <param name="filled">Should ellipse be filled.</param>
+        /// <returns>Coordinates for ellipse.</returns>
+        public IEnumerable<Coordinates> CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness, bool filled)
         {
             List<Coordinates> output = new List<Coordinates>();
             IEnumerable<Coordinates> outline = CreateEllipse(startCoordinates, endCoordinates, thickness);
@@ -62,10 +61,10 @@ namespace PixiEditor.Models.Tools.Tools
         /// <summary>
         ///     Calculates ellipse points for specified coordinates and thickness.
         /// </summary>
-        /// <param name="startCoordinates">Top left coordinate of ellipse</param>
-        /// <param name="endCoordinates">Bottom right coordinate of ellipse</param>
-        /// <param name="thickness">Thickness of ellipse</param>
-        /// <returns>Coordinates for ellipse</returns>
+        /// <param name="startCoordinates">Top left coordinate of ellipse.</param>
+        /// <param name="endCoordinates">Bottom right coordinate of ellipse.</param>
+        /// <param name="thickness">Thickness of ellipse.</param>
+        /// <returns>Coordinates for ellipse.</returns>
         public IEnumerable<Coordinates> CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness)
         {
             double radiusX = (endCoordinates.X - startCoordinates.X) / 2.0;
@@ -112,9 +111,9 @@ namespace PixiEditor.Models.Tools.Tools
                 // calculate next pixel coords
                 currentX++;
 
-                if (Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX, 2) +
-                    Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY - 0.5, 2) -
-                    Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2) >= 0)
+                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX, 2)) +
+                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY - 0.5, 2)) -
+                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) >= 0)
                 {
                     currentY--;
                 }
@@ -123,7 +122,8 @@ namespace PixiEditor.Models.Tools.Tools
                 double derivativeX = 2 * Math.Pow(halfHeight, 2) * (currentX - centerX);
                 double derivativeY = 2 * Math.Pow(halfWidth, 2) * (currentY - centerY);
                 currentSlope = -(derivativeX / derivativeY);
-            } while (currentSlope > -1 && currentY - centerY > 0.5);
+            }
+            while (currentSlope > -1 && currentY - centerY > 0.5);
 
             // from middle to 0
             while (currentY - centerY >= 0)
@@ -131,9 +131,9 @@ namespace PixiEditor.Models.Tools.Tools
                 outputCoordinates.AddRange(GetRegionPoints(currentX, centerX, currentY, centerY));
 
                 currentY--;
-                if (Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2) +
-                    Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY, 2) -
-                    Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2) < 0)
+                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2)) +
+                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY, 2)) -
+                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) < 0)
                 {
                     currentX++;
                 }

+ 1 - 2
PixiEditor/Models/Tools/Tools/ColorPickerTool.cs

@@ -27,8 +27,7 @@ namespace PixiEditor.Models.Tools.Tools
             {
                 using (Graphics graphics = Graphics.FromImage(bitmap))
                 {
-                    graphics.CopyFromScreen(MousePositionConverter.GetCursorPosition(), new Point(0, 0),
-                        new Size(1, 1));
+                    graphics.CopyFromScreen(MousePositionConverter.GetCursorPosition(), new Point(0, 0), new Size(1, 1));
                 }
 
                 color = bitmap.GetPixel(0, 0);

+ 11 - 10
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -24,8 +24,11 @@ namespace PixiEditor.Models.Tools.Tools
         {
             BitmapPixelChanges pixels =
                 BitmapPixelChanges.FromSingleColoredArray(
-                    CreateLine(coordinates,
-                        Toolbar.GetSetting<SizeSetting>("ToolSize").Value, CapType.Square, CapType.Square), color);
+                    CreateLine(
+                        coordinates,
+                        Toolbar.GetSetting<SizeSetting>("ToolSize").Value,
+                        CapType.Square,
+                        CapType.Square), color);
             return Only(pixels, layer);
         }
 
@@ -34,8 +37,7 @@ namespace PixiEditor.Models.Tools.Tools
             return CreateLine(new[] { end, start }, thickness, CapType.Square, CapType.Square);
         }
 
-        public IEnumerable<Coordinates> CreateLine(Coordinates start, Coordinates end, int thickness, CapType startCap,
-            CapType endCap)
+        public IEnumerable<Coordinates> CreateLine(Coordinates start, Coordinates end, int thickness, CapType startCap, CapType endCap)
         {
             return CreateLine(new[] { end, start }, thickness, startCap, endCap);
         }
@@ -46,8 +48,7 @@ namespace PixiEditor.Models.Tools.Tools
             Coordinates latestCoordinates = coordinates[0];
             if (thickness == 1)
             {
-                return BresenhamLine(startingCoordinates.X, startingCoordinates.Y, latestCoordinates.X,
-                    latestCoordinates.Y);
+                return BresenhamLine(startingCoordinates.X, startingCoordinates.Y, latestCoordinates.X, latestCoordinates.Y);
             }
 
             return GetLinePoints(startingCoordinates, latestCoordinates, thickness, startCap, endCap);
@@ -82,17 +83,17 @@ namespace PixiEditor.Models.Tools.Tools
                     {
                         return GetRoundCap(position, thickness); // Round cap is not working very well, circle tool must be improved
                     }
+
                 default:
                     return GetThickShape(new[] { position }, thickness);
             }
         }
 
         /// <summary>
-        ///     Gets points for rounded cap on specified position and thickness
+        ///     Gets points for rounded cap on specified position and thickness.
         /// </summary>
-        /// <param name="position">Starting position of cap</param>
-        /// <param name="thickness">Thickness of cap</param>
-        /// <returns></returns>
+        /// <param name="position">Starting position of cap.</param>
+        /// <param name="thickness">Thickness of cap.</param>
         private IEnumerable<Coordinates> GetRoundCap(Coordinates position, int thickness)
         {
             CircleTool circle = new CircleTool();

+ 17 - 16
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -135,6 +135,22 @@ namespace PixiEditor.Models.Tools.Tools
             return result;
         }
 
+        public BitmapPixelChanges MoveSelection(Layer layer, Coordinates[] mouseMove)
+        {
+            Coordinates end = mouseMove[0];
+
+            currentSelection = TranslateSelection(end, out Coordinates[] previousSelection);
+            if (updateViewModelSelection)
+            {
+                ViewModelMain.Current.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
+            }
+
+            ClearSelectedPixels(layer, previousSelection);
+
+            lastMouseMove = end;
+            return BitmapPixelChanges.FromArrays(currentSelection, startPixelColors[layer]);
+        }
+
         private void ApplyOffsets(object[] parameters)
         {
             Dictionary<Layer, Thickness> offsets = (Dictionary<Layer, Thickness>)parameters[0];
@@ -165,22 +181,6 @@ namespace PixiEditor.Models.Tools.Tools
             return pixels;
         }
 
-        public BitmapPixelChanges MoveSelection(Layer layer, Coordinates[] mouseMove)
-        {
-            Coordinates end = mouseMove[0];
-
-            currentSelection = TranslateSelection(end, out Coordinates[] previousSelection);
-            if (updateViewModelSelection)
-            {
-                ViewModelMain.Current.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
-            }
-
-            ClearSelectedPixels(layer, previousSelection);
-
-            lastMouseMove = end;
-            return BitmapPixelChanges.FromArrays(currentSelection, startPixelColors[layer]);
-        }
-
         private void ResetSelectionValues(Coordinates start)
         {
             lastStartMousePos = start;
@@ -197,6 +197,7 @@ namespace PixiEditor.Models.Tools.Tools
             previousSelection = currentSelection.ToArray();
             return Transform.Translate(previousSelection, translation);
         }
+
         private void ClearSelectedPixels(Layer layer, Coordinates[] selection)
         {
             if (!clearedPixels.ContainsKey(layer) || clearedPixels[layer] == false)

+ 21 - 21
PixiEditor/Models/Tools/Tools/RectangleTool.cs

@@ -30,8 +30,8 @@ namespace PixiEditor.Models.Tools.Tools
             {
                 Color fillColor = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
                 pixels.ChangedPixels.AddRangeOverride(
-                    BitmapPixelChanges.FromSingleColoredArray
-                            (CalculateFillForRectangle(coordinates[^1], coordinates[0], thickness), fillColor)
+                    BitmapPixelChanges.FromSingleColoredArray(
+                            CalculateFillForRectangle(coordinates[^1], coordinates[0], thickness), fillColor)
                         .ChangedPixels);
             }
 
@@ -67,25 +67,6 @@ namespace PixiEditor.Models.Tools.Tools
             return CreateRectangle(new[] { end, start }, thickness);
         }
 
-        private IEnumerable<Coordinates> CalculateRectanglePoints(DoubleCords coordinates)
-        {
-            List<Coordinates> finalCoordinates = new List<Coordinates>();
-
-            for (int i = coordinates.Coords1.X; i < coordinates.Coords2.X + 1; i++)
-            {
-                finalCoordinates.Add(new Coordinates(i, coordinates.Coords1.Y));
-                finalCoordinates.Add(new Coordinates(i, coordinates.Coords2.Y));
-            }
-
-            for (int i = coordinates.Coords1.Y + 1; i <= coordinates.Coords2.Y - 1; i++)
-            {
-                finalCoordinates.Add(new Coordinates(coordinates.Coords1.X, i));
-                finalCoordinates.Add(new Coordinates(coordinates.Coords2.X, i));
-            }
-
-            return finalCoordinates;
-        }
-
         public IEnumerable<Coordinates> CalculateFillForRectangle(Coordinates start, Coordinates end, int thickness)
         {
             int offset = (int)Math.Ceiling(thickness / 2f);
@@ -118,5 +99,24 @@ namespace PixiEditor.Models.Tools.Tools
 
             return filledCoordinates.Distinct();
         }
+
+        private IEnumerable<Coordinates> CalculateRectanglePoints(DoubleCords coordinates)
+        {
+            List<Coordinates> finalCoordinates = new List<Coordinates>();
+
+            for (int i = coordinates.Coords1.X; i < coordinates.Coords2.X + 1; i++)
+            {
+                finalCoordinates.Add(new Coordinates(i, coordinates.Coords1.Y));
+                finalCoordinates.Add(new Coordinates(i, coordinates.Coords2.Y));
+            }
+
+            for (int i = coordinates.Coords1.Y + 1; i <= coordinates.Coords2.Y - 1; i++)
+            {
+                finalCoordinates.Add(new Coordinates(coordinates.Coords1.X, i));
+                finalCoordinates.Add(new Coordinates(coordinates.Coords2.X, i));
+            }
+
+            return finalCoordinates;
+        }
     }
 }

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

@@ -1,91 +1,91 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Controls;
-using System.Windows.Input;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class SelectTool : ReadonlyTool
-    {
-        private Selection oldSelection;
-        public SelectionType SelectionType = SelectionType.Add;
-
-        public SelectTool()
-        {
-            Tooltip = "Selects area. (M)";
-            Toolbar = new SelectToolToolbar();
-        }
-
-        public override ToolType ToolType => ToolType.Select;
-
-        public override void OnMouseDown(MouseEventArgs e)
-        {
-            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out SelectionType);
-
-            oldSelection = null;
-            if (ViewModelMain.Current.ActiveSelection != null &&
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Controls;
+using System.Windows.Input;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class SelectTool : ReadonlyTool
+    {
+        private Selection oldSelection;
+
+        public SelectTool()
+        {
+            Tooltip = "Selects area. (M)";
+            Toolbar = new SelectToolToolbar();
+        }
+
+        public SelectionType SelectionType { get; set; } = SelectionType.Add;
+
+        public override ToolType ToolType => ToolType.Select;
+
+        public override void OnMouseDown(MouseEventArgs e)
+        {
+            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("Mode")?.Value as ComboBoxItem)?.Content as string, out SelectionType selectionType);
+            SelectionType = selectionType;
+
+            oldSelection = null;
+            if (ViewModelMain.Current.ActiveSelection != null &&
                 ViewModelMain.Current.ActiveSelection.SelectedPoints != null)
             {
                 oldSelection = ViewModelMain.Current.ActiveSelection;
             }
-        }
-
-        public override void OnMouseUp(MouseEventArgs e)
-        {
+        }
+
+        public override void OnMouseUp(MouseEventArgs e)
+        {
             if (ViewModelMain.Current.ActiveSelection.SelectedPoints.Count() <= 1)
             {
                 // If we have not selected multiple points, clear the selection
                 ViewModelMain.Current.ActiveSelection.Clear();
             }
 
-            UndoManager.AddUndoChange(new Change("ActiveSelection", oldSelection,
-                ViewModelMain.Current.ActiveSelection, "Select pixels"));
-        }
-
-        public override void Use(Coordinates[] pixels)
-        {
-            Select(pixels);
-        }
-
-        private void Select(Coordinates[] pixels)
-        {
-            IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
-            ViewModelMain.Current.ActiveSelection.SetSelection(selection, SelectionType);
-        }
-
-        public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
-        {
-            RectangleTool rectangleTool = new RectangleTool();
-            List<Coordinates> selection = rectangleTool.CreateRectangle(start, end, 1).ToList();
-            selection.AddRange(rectangleTool.CalculateFillForRectangle(start, end, 1));
-            return selection;
-        }
-
-        /// <summary>
-        ///     Gets coordinates of every pixel in root layer
-        /// </summary>
-        /// <returns>Coordinates array of pixels</returns>
-        public IEnumerable<Coordinates> GetAllSelection()
-        {
-            return GetAllSelection(ViewModelMain.Current.BitmapManager.ActiveDocument);
-        }
-
-        /// <summary>
-        ///     Gets coordinates of every pixel in chosen document
-        /// </summary>
-        /// <param name="document"></param>
-        /// <returns>Coordinates array of pixels</returns>
-        public IEnumerable<Coordinates> GetAllSelection(Document document)
-        {
-            return GetRectangleSelectionForPoints(new Coordinates(0, 0), new Coordinates(document.Width - 1, document.Height - 1));
-        }
-    }
+            UndoManager.AddUndoChange(new Change("ActiveSelection", oldSelection, ViewModelMain.Current.ActiveSelection, "Select pixels"));
+        }
+
+        public override void Use(Coordinates[] pixels)
+        {
+            Select(pixels);
+        }
+
+        public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
+        {
+            RectangleTool rectangleTool = new RectangleTool();
+            List<Coordinates> selection = rectangleTool.CreateRectangle(start, end, 1).ToList();
+            selection.AddRange(rectangleTool.CalculateFillForRectangle(start, end, 1));
+            return selection;
+        }
+
+        /// <summary>
+        ///     Gets coordinates of every pixel in root layer.
+        /// </summary>
+        /// <returns>Coordinates array of pixels.</returns>
+        public IEnumerable<Coordinates> GetAllSelection()
+        {
+            return GetAllSelection(ViewModelMain.Current.BitmapManager.ActiveDocument);
+        }
+
+        /// <summary>
+        ///     Gets coordinates of every pixel in chosen document.
+        /// </summary>
+        /// <returns>Coordinates array of pixels.</returns>
+        public IEnumerable<Coordinates> GetAllSelection(Document document)
+        {
+            return GetRectangleSelectionForPoints(new Coordinates(0, 0), new Coordinates(document.Width - 1, document.Height - 1));
+        }
+
+        private void Select(Coordinates[] pixels)
+        {
+            IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
+            ViewModelMain.Current.ActiveSelection.SetSelection(selection, SelectionType);
+        }
+    }
 }

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

@@ -1,51 +1,51 @@
-using System;
-using System.Windows;
-using System.Windows.Input;
-using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class ZoomTool : ReadonlyTool
-    {
-        public const float ZoomSensitivityMultiplier = 30f;
-        private readonly double pixelsPerZoomMultiplier;
-        private double startingX;
-        private readonly double workAreaWidth = SystemParameters.WorkArea.Width;
-
-        public ZoomTool()
-        {
-            HideHighlight = true;
-            CanStartOutsideCanvas = true;
-            Tooltip = "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
-            pixelsPerZoomMultiplier = workAreaWidth / ZoomSensitivityMultiplier;
-        }
-
-        public override ToolType ToolType => ToolType.Zoom;
-
-        public override void OnMouseDown(MouseEventArgs e)
-        {
-            startingX = MousePositionConverter.GetCursorPosition().X;
-            ViewModelMain.Current.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
-        }
-
-        public override void OnMouseMove(MouseEventArgs e)
-        {
-            if (e.LeftButton == MouseButtonState.Pressed)
-            {
-                double xPos = MousePositionConverter.GetCursorPosition().X;
-
-                double rawPercentDifference = (xPos - startingX) / pixelsPerZoomMultiplier; // negative - zoom out, positive - zoom in, linear
-                double finalPercentDifference = Math.Pow(2, rawPercentDifference) * 100.0; // less than 100 - zoom out, greater than 100 - zoom in
-                Zoom(finalPercentDifference);
-            }
-        }
-
-        public override void OnMouseUp(MouseEventArgs e)
-        {
-            if (e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released &&
-                startingX == MousePositionConverter.GetCursorPosition().X)
-            {
+using System;
+using System.Windows;
+using System.Windows.Input;
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class ZoomTool : ReadonlyTool
+    {
+        public const float ZoomSensitivityMultiplier = 30f;
+        private readonly double pixelsPerZoomMultiplier;
+        private readonly double workAreaWidth = SystemParameters.WorkArea.Width;
+        private double startingX;
+
+        public ZoomTool()
+        {
+            HideHighlight = true;
+            CanStartOutsideCanvas = true;
+            Tooltip = "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
+            pixelsPerZoomMultiplier = workAreaWidth / ZoomSensitivityMultiplier;
+        }
+
+        public override ToolType ToolType => ToolType.Zoom;
+
+        public override void OnMouseDown(MouseEventArgs e)
+        {
+            startingX = MousePositionConverter.GetCursorPosition().X;
+            ViewModelMain.Current.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
+        }
+
+        public override void OnMouseMove(MouseEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Pressed)
+            {
+                double xPos = MousePositionConverter.GetCursorPosition().X;
+
+                double rawPercentDifference = (xPos - startingX) / pixelsPerZoomMultiplier; // negative - zoom out, positive - zoom in, linear
+                double finalPercentDifference = Math.Pow(2, rawPercentDifference) * 100.0; // less than 100 - zoom out, greater than 100 - zoom in
+                Zoom(finalPercentDifference);
+            }
+        }
+
+        public override void OnMouseUp(MouseEventArgs e)
+        {
+            if (e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released &&
+                startingX == MousePositionConverter.GetCursorPosition().X)
+            {
                 if (Keyboard.Modifiers.HasFlag(ModifierKeys.Alt))
                 {
                     Zoom(85);
@@ -54,16 +54,16 @@ namespace PixiEditor.Models.Tools.Tools
                 {
                     Zoom(115);
                 }
-            }
-        }
-
-        public void Zoom(double percentage)
-        {
-            ViewModelMain.Current.ZoomPercentage = percentage;
-        }
-
-        public override void Use(Coordinates[] pixels)
-        {
-        }
-    }
+            }
+        }
+
+        public void Zoom(double percentage)
+        {
+            ViewModelMain.Current.ZoomPercentage = percentage;
+        }
+
+        public override void Use(Coordinates[] pixels)
+        {
+        }
+    }
 }

+ 15 - 14
PixiEditor/NotifyableObject.cs

@@ -1,19 +1,20 @@
-using System;
-using System.ComponentModel;
-
-namespace PixiEditor.Helpers
-{
-    [Serializable]
-    public class NotifyableObject : INotifyPropertyChanged
-    {
-        [field: NonSerialized] public event PropertyChangedEventHandler PropertyChanged = delegate { };
-
-        protected void RaisePropertyChanged(string property)
-        {
+using System;
+using System.ComponentModel;
+
+namespace PixiEditor.Helpers
+{
+    [Serializable]
+    public class NotifyableObject : INotifyPropertyChanged
+    {
+        [field: NonSerialized]
+        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
+
+        protected void RaisePropertyChanged(string property)
+        {
             if (property != null)
             {
                 PropertyChanged(this, new PropertyChangedEventArgs(property));
             }
-        }
-    }
+        }
+    }
 }

+ 6 - 6
PixiEditor/Properties/AssemblyInfo.cs

@@ -27,14 +27,15 @@ using System.Windows;
 // the line below to match the UICulture setting in the project file.
 
 // [assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
-
 [assembly: ThemeInfo(
     ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located
-                                     // (used if a resource is not found in the page,
-                                     // or application resource dictionaries)
+
+    // (used if a resource is not found in the page,
+    // or application resource dictionaries)
     ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located
-                                               // (used if a resource is not found in the page,
-                                               // app, or any theme specific resource dictionaries)
+
+// (used if a resource is not found in the page,
+// app, or any theme specific resource dictionaries)
 ]
 
 // Version information for an assembly consists of the following four values:
@@ -47,6 +48,5 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-
 [assembly: AssemblyVersion("0.1.3.0")]
 [assembly: AssemblyFileVersion("0.1.3.0")]

+ 1 - 1
PixiEditor/ViewModels/ImportFilePopupViewModel.cs

@@ -102,7 +102,7 @@ namespace PixiEditor.ViewModels
         }
 
         /// <summary>
-        ///     Command that handles Path choosing to save file
+        ///     Command that handles Path choosing to save file.
         /// </summary>
         /// <param name="parameter"></param>
         private void ChoosePath(object parameter)

+ 1 - 1
PixiEditor/ViewModels/SaveFilePopupViewModel.cs

@@ -68,7 +68,7 @@ namespace PixiEditor.ViewModels
         }
 
         /// <summary>
-        ///     Command that handles Path choosing to save file
+        ///     Command that handles Path choosing to save file.
         /// </summary>
         /// <param name="parameter"></param>
         private void ChoosePath(object parameter)

+ 25 - 25
PixiEditor/ViewModels/ViewModelBase.cs

@@ -1,34 +1,34 @@
-using System.ComponentModel;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-
-namespace PixiEditor.ViewModels
-{
-    public class ViewModelBase : INotifyPropertyChanged
-    {
-        public event PropertyChangedEventHandler PropertyChanged = delegate { };
-
-        protected void RaisePropertyChanged(string property)
-        {
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+
+namespace PixiEditor.ViewModels
+{
+    public class ViewModelBase : INotifyPropertyChanged
+    {
+        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
+
+        protected void RaisePropertyChanged(string property)
+        {
             if (property != null)
             {
                 PropertyChanged(this, new PropertyChangedEventArgs(property));
             }
-        }
-
-        protected void CloseButton(object parameter)
-        {
-            ((Window)parameter).Close();
-        }
-
-        protected void DragMove(object parameter)
-        {
-            Window popup = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
+        }
+
+        protected void CloseButton(object parameter)
+        {
+            ((Window)parameter).Close();
+        }
+
+        protected void DragMove(object parameter)
+        {
+            Window popup = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
             if (Mouse.LeftButton == MouseButtonState.Pressed)
             {
                 popup.DragMove();
             }
-        }
-    }
+        }
+    }
 }

+ 338 - 346
PixiEditor/ViewModels/ViewModelMain.cs

@@ -126,6 +126,7 @@ namespace PixiEditor.ViewModels
                     new Shortcut(Key.OemMinus, ZoomCommand, 85),
                     new Shortcut(Key.OemOpenBrackets, ChangeToolSizeCommand, -1),
                     new Shortcut(Key.OemCloseBrackets, ChangeToolSizeCommand, 1),
+
                     // Editor
                     new Shortcut(Key.X, SwapColorsCommand),
                     new Shortcut(Key.Y, RedoCommand, modifier: ModifierKeys.Control),
@@ -140,9 +141,12 @@ namespace PixiEditor.ViewModels
                     new Shortcut(Key.I, OpenResizePopupCommand, modifier: ModifierKeys.Control | ModifierKeys.Shift),
                     new Shortcut(Key.C, OpenResizePopupCommand, "canvas", ModifierKeys.Control | ModifierKeys.Shift),
                     new Shortcut(Key.F11, SystemCommands.MaximizeWindowCommand),
+
                     // File
                     new Shortcut(Key.O, OpenFileCommand, modifier: ModifierKeys.Control),
-                    new Shortcut(Key.S, ExportFileCommand,
+                    new Shortcut(
+                        Key.S,
+                        ExportFileCommand,
                         modifier: ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt),
                     new Shortcut(Key.S, SaveDocumentCommand, modifier: ModifierKeys.Control),
                     new Shortcut(Key.S, SaveDocumentCommand, "AsNew", ModifierKeys.Control | ModifierKeys.Shift),
@@ -157,10 +161,10 @@ namespace PixiEditor.ViewModels
             InitUpdateChecker();
         }
 
-        public Action CloseAction { get; set; }
-
         public static ViewModelMain Current { get; set; }
 
+        public Action CloseAction { get; set; }
+
         public RelayCommand SelectToolCommand { get; set; } // Command that handles tool switching
 
         public RelayCommand OpenNewFilePopupCommand { get; set; } // Command that generates draw area
@@ -363,12 +367,6 @@ namespace PixiEditor.ViewModels
 
         public UpdateChecker UpdateChecker { get; set; }
 
-        private void RestartApplication(object parameter)
-        {
-            Process.Start(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "PixiEditor.UpdateInstaller.exe"));
-            Application.Current.Shutdown();
-        }
-
         public async Task<bool> CheckForUpdate()
         {
             return await Task.Run(async () =>
@@ -388,187 +386,6 @@ namespace PixiEditor.ViewModels
             });
         }
 
-        private void InitUpdateChecker()
-        {
-            Assembly assembly = Assembly.GetExecutingAssembly();
-            FileVersionInfo info = FileVersionInfo.GetVersionInfo(assembly.Location);
-            UpdateChecker = new UpdateChecker(info.FileVersion);
-            VersionText = $"Version {info.FileVersion}";
-        }
-
-        private void ZoomViewport(object parameter)
-        {
-            double zoom = (int)parameter;
-            ZoomPercentage = zoom;
-            ZoomPercentage = 100;
-        }
-
-        private void ChangeToolSize(object parameter)
-        {
-            int increment = (int)parameter;
-            int newSize = BitmapManager.ToolSize + increment;
-            if (newSize > 0)
-            {
-                BitmapManager.ToolSize = newSize;
-            }
-        }
-
-        private void OpenHyperlink(object parameter)
-        {
-            if (parameter == null)
-            {
-                return;
-            }
-
-            string url = (string)parameter;
-            ProcessStartInfo processInfo = new ProcessStartInfo
-            {
-                FileName = url,
-                UseShellExecute = true
-            };
-            Process.Start(processInfo);
-        }
-
-        private void CenterContent(object property)
-        {
-            BitmapManager.ActiveDocument.CenterContent();
-        }
-
-        private void CloseWindow(object property)
-        {
-            if (!(property is CancelEventArgs))
-            {
-                throw new ArgumentException();
-            } ((CancelEventArgs)property).Cancel = true;
-
-            ConfirmationType result = ConfirmationType.No;
-            if (unsavedDocumentModified)
-            {
-                result = ConfirmationDialog.Show(ConfirmationDialogMessage);
-                if (result == ConfirmationType.Yes)
-                {
-                    SaveDocument(null);
-                }
-            }
-
-            if (result != ConfirmationType.Canceled)
-            {
-                ((CancelEventArgs)property).Cancel = false;
-            }
-        }
-
-        private async void OnStartup(object parameter)
-        {
-            string lastArg = Environment.GetCommandLineArgs().Last();
-            if (Importer.IsSupportedFile(lastArg) && File.Exists(lastArg))
-            {
-                Open(lastArg);
-            }
-            else
-            {
-                OpenNewFilePopup(null);
-            }
-
-            await CheckForUpdate();
-        }
-
-        private void BitmapManager_DocumentChanged(object sender, DocumentChangedEventArgs e)
-        {
-            e.NewDocument.DocumentSizeChanged += ActiveDocument_DocumentSizeChanged;
-        }
-
-        private void Open(object property)
-        {
-            OpenFileDialog dialog = new OpenFileDialog
-            {
-                Filter = "All Files|*.*|PixiEditor Files | *.pixi|PNG Files|*.png",
-                DefaultExt = "pixi"
-            };
-            if ((bool)dialog.ShowDialog())
-            {
-                if (Importer.IsSupportedFile(dialog.FileName))
-                {
-                    Open(dialog.FileName);
-                }
-
-                RecenterZoombox = !RecenterZoombox;
-            }
-        }
-
-        private void Open(string path)
-        {
-            if (unsavedDocumentModified)
-            {
-                ConfirmationType result = ConfirmationDialog.Show(ConfirmationDialogMessage);
-                if (result == ConfirmationType.Yes)
-                {
-                    SaveDocument(null);
-                }
-                else if (result == ConfirmationType.Canceled)
-                {
-                    return;
-                }
-            }
-
-            ResetProgramStateValues();
-            if (path.EndsWith(".pixi"))
-            {
-                OpenDocument(path);
-            }
-            else
-            {
-                OpenFile(path);
-            }
-        }
-
-        private void OpenDocument(string path)
-        {
-            BitmapManager.ActiveDocument = Importer.ImportDocument(path);
-            Exporter.SaveDocumentPath = path;
-            unsavedDocumentModified = false;
-        }
-
-        private void SaveDocument(object parameter)
-        {
-            bool paramIsAsNew = parameter != null && parameter.ToString()?.ToLower() == "asnew";
-            if (paramIsAsNew || Exporter.SaveDocumentPath == null)
-            {
-                bool saved = Exporter.SaveAsEditableFileWithDialog(BitmapManager.ActiveDocument, !paramIsAsNew);
-                unsavedDocumentModified = unsavedDocumentModified && !saved;
-            }
-            else
-            {
-                Exporter.SaveAsEditableFile(BitmapManager.ActiveDocument, Exporter.SaveDocumentPath);
-                unsavedDocumentModified = false;
-            }
-        }
-
-        private void RemoveSwatch(object parameter)
-        {
-            if (!(parameter is Color))
-            {
-                throw new ArgumentException();
-            }
-
-            Color color = (Color)parameter;
-            if (BitmapManager.ActiveDocument.Swatches.Contains(color))
-            {
-                BitmapManager.ActiveDocument.Swatches.Remove(color);
-            }
-        }
-
-        private void SelectColor(object parameter)
-        {
-            PrimaryColor = parameter as Color? ?? throw new ArgumentException();
-        }
-
-        private void ActiveDocument_DocumentSizeChanged(object sender, DocumentSizeChangedEventArgs e)
-        {
-            ActiveSelection = new Selection(Array.Empty<Coordinates>());
-            RecenterZoombox = !RecenterZoombox;
-            unsavedDocumentModified = true;
-        }
-
         public void AddSwatch(Color color)
         {
             if (!BitmapManager.ActiveDocument.Swatches.Contains(color))
@@ -577,32 +394,6 @@ namespace PixiEditor.ViewModels
             }
         }
 
-        private void OpenResizePopup(object parameter)
-        {
-            bool isCanvasDialog = (string)parameter == "canvas";
-            ResizeDocumentDialog dialog = new ResizeDocumentDialog(
-                BitmapManager.ActiveDocument.Width,
-                BitmapManager.ActiveDocument.Height,
-                isCanvasDialog);
-            if (dialog.ShowDialog())
-            {
-                if (isCanvasDialog)
-                {
-                    BitmapManager.ActiveDocument.ResizeCanvas(dialog.Width, dialog.Height, dialog.ResizeAnchor);
-                }
-                else
-                {
-                    BitmapManager.ActiveDocument.Resize(dialog.Width, dialog.Height);
-                }
-            }
-        }
-
-        private void DeletePixels(object parameter)
-        {
-            BitmapManager.BitmapOperations.DeletePixels(new[] { BitmapManager.ActiveLayer },
-                ActiveSelection.SelectedPoints.ToArray());
-        }
-
         public void ClipCanvas(object parameter)
         {
             BitmapManager.ActiveDocument?.ClipCanvas();
@@ -618,7 +409,8 @@ namespace PixiEditor.ViewModels
         {
             Copy(null);
             BitmapManager.ActiveLayer.SetPixels(
-                BitmapPixelChanges.FromSingleColoredArray(ActiveSelection.SelectedPoints.ToArray(),
+                BitmapPixelChanges.FromSingleColoredArray(
+                    ActiveSelection.SelectedPoints.ToArray(),
                     Colors.Transparent));
         }
 
@@ -627,28 +419,12 @@ namespace PixiEditor.ViewModels
             ClipboardController.PasteFromClipboard();
         }
 
-        private bool CanPaste(object property)
-        {
-            return DocumentIsNotNull(null) && ClipboardController.IsImageInClipboard();
-        }
-
-        private void Copy(object parameter)
-        {
-            ClipboardController.CopyToClipboard(BitmapManager.ActiveDocument.Layers.ToArray(),
-                ActiveSelection.SelectedPoints.ToArray(), BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height);
-        }
-
         public void SelectAll(object parameter)
         {
             SelectTool select = new SelectTool();
             ActiveSelection.SetSelection(select.GetAllSelection(), SelectionType.New);
         }
 
-        private bool CanSelectAll(object property)
-        {
-            return BitmapManager.ActiveDocument != null && BitmapManager.ActiveDocument.Layers.Count > 0;
-        }
-
         public bool DocumentIsNotNull(object property)
         {
             return BitmapManager.ActiveDocument != null;
@@ -659,11 +435,6 @@ namespace PixiEditor.ViewModels
             ActiveSelection?.Clear();
         }
 
-        private bool SelectionIsNotEmpty(object property)
-        {
-            return ActiveSelection?.SelectedPoints != null && ActiveSelection.SelectedPoints.Count > 0;
-        }
-
         public void SetTool(object parameter)
         {
             SetActiveTool((ToolType)parameter);
@@ -674,17 +445,6 @@ namespace PixiEditor.ViewModels
             BitmapManager.ActiveDocument.Layers[(int)parameter].IsRenaming = true;
         }
 
-        private void KeyUp(object parameter)
-        {
-            KeyEventArgs args = (KeyEventArgs)parameter;
-            if (restoreToolOnKeyUp && ShortcutController.LastShortcut != null && ShortcutController.LastShortcut.ShortcutKey == args.Key)
-            {
-                restoreToolOnKeyUp = false;
-                SetActiveTool(lastActionTool);
-                ShortcutController.BlockShortcutExecution = false;
-            }
-        }
-
         public void KeyDown(object parameter)
         {
             KeyEventArgs args = (KeyEventArgs)parameter;
@@ -697,11 +457,6 @@ namespace PixiEditor.ViewModels
             ShortcutController.KeyPressed(args.Key, Keyboard.Modifiers);
         }
 
-        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
-        {
-            TriggerNewUndoChange(BitmapManager.SelectedTool);
-        }
-
         public void TriggerNewUndoChange(Tool toolUsed)
         {
             if (BitmapManager.IsOperationTool(toolUsed)
@@ -718,18 +473,7 @@ namespace PixiEditor.ViewModels
             }
         }
 
-        private void BitmapUtility_BitmapChanged(object sender, BitmapChangedEventArgs e)
-        {
-            ChangesController.AddChanges(new LayerChange(e.PixelsChanged, e.ChangedLayerIndex),
-                new LayerChange(e.OldPixelsValues, e.ChangedLayerIndex));
-            unsavedDocumentModified = true;
-            if (BitmapManager.IsOperationTool())
-            {
-                AddSwatch(PrimaryColor);
-            }
-        }
-
-        public void SwapColors(object parameter)
+        public void SwapColors(object parameter)
         {
             Color tmp = PrimaryColor;
             PrimaryColor = SecondaryColor;
@@ -801,42 +545,6 @@ namespace PixiEditor.ViewModels
             SetToolCursor(tool.ToolType);
         }
 
-        private void SetToolCursor(ToolType tool)
-        {
-            if (tool != ToolType.None)
-            {
-                ToolCursor = BitmapManager.SelectedTool.Cursor;
-            }
-            else
-            {
-                ToolCursor = Cursors.Arrow;
-            }
-        }
-
-        private void MouseDown(object parameter)
-        {
-            if (BitmapManager.ActiveDocument.Layers.Count == 0)
-            {
-                return;
-            }
-
-            if (Mouse.LeftButton == MouseButtonState.Pressed)
-            {
-                if (!BitmapManager.MouseController.IsRecordingChanges)
-                {
-                    bool clickedOnCanvas = MouseXOnCanvas >= 0 && MouseXOnCanvas <= BitmapManager.ActiveDocument.Width &&
-                                          MouseYOnCanvas >= 0 && MouseYOnCanvas <= BitmapManager.ActiveDocument.Height;
-                    BitmapManager.MouseController.StartRecordingMouseMovementChanges(clickedOnCanvas);
-                    BitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
-                }
-            }
-
-            // 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;
-        }
-
         // this is public for testing.
         public void MouseHook_OnMouseUp(object sender, Point p)
         {
@@ -845,26 +553,8 @@ namespace PixiEditor.ViewModels
         }
 
         /// <summary>
-        ///     Method connected with command, it executes tool "activity"
-        /// </summary>
-        /// <param name="parameter"></param>
-        private void MouseMove(object parameter)
-        {
-            Coordinates cords = new Coordinates((int)MouseXOnCanvas, (int)MouseYOnCanvas);
-            MousePositionConverter.CurrentCoordinates = cords;
-
-            if (BitmapManager.MouseController.IsRecordingChanges && Mouse.LeftButton == MouseButtonState.Pressed)
-            {
-                BitmapManager.MouseController.RecordMouseMovementChange(cords);
-            }
-
-            BitmapManager.MouseController.MouseMoved(cords);
-        }
-
-        /// <summary>
-        ///     Generates new Layer and sets it as active one
+        ///     Generates new Layer and sets it as active one.
         /// </summary>
-        /// <param name="parameter"></param>
         public void OpenNewFilePopup(object parameter)
         {
             NewFileDialog newFile = new NewFileDialog();
@@ -877,7 +567,6 @@ namespace PixiEditor.ViewModels
         /// <summary>
         ///     Opens file from path.
         /// </summary>
-        /// <param name="path"></param>
         public void OpenFile(string path)
         {
             ImportFileDialog dialog = new ImportFileDialog();
@@ -929,12 +618,9 @@ namespace PixiEditor.ViewModels
             return BitmapManager.ActiveDocument != null && BitmapManager.ActiveDocument.Layers.Count > 0;
         }
 
-        #region Undo/Redo
-
         /// <summary>
-        ///     Undo last action
+        ///     Undo last action.
         /// </summary>
-        /// <param name="parameter"></param>
         public void Undo(object parameter)
         {
             Deselect(null);
@@ -942,42 +628,352 @@ namespace PixiEditor.ViewModels
         }
 
         /// <summary>
-        ///     Returns true if undo can be done.
+        ///     Redo last action.
         /// </summary>
-        /// <param name="property"></param>
-        /// <returns></returns>
-        private bool CanUndo(object property)
+        public void Redo(object parameter)
         {
-            return UndoManager.CanUndo;
+            UndoManager.Redo();
+        }
+
+        private void RestartApplication(object parameter)
+        {
+            Process.Start(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "PixiEditor.UpdateInstaller.exe"));
+            Application.Current.Shutdown();
+        }
+
+        private void InitUpdateChecker()
+        {
+            Assembly assembly = Assembly.GetExecutingAssembly();
+            FileVersionInfo info = FileVersionInfo.GetVersionInfo(assembly.Location);
+            UpdateChecker = new UpdateChecker(info.FileVersion);
+            VersionText = $"Version {info.FileVersion}";
+        }
+
+        private void ZoomViewport(object parameter)
+        {
+            double zoom = (int)parameter;
+            ZoomPercentage = zoom;
+            ZoomPercentage = 100;
+        }
+
+        private void ChangeToolSize(object parameter)
+        {
+            int increment = (int)parameter;
+            int newSize = BitmapManager.ToolSize + increment;
+            if (newSize > 0)
+            {
+                BitmapManager.ToolSize = newSize;
+            }
+        }
+
+        private void OpenHyperlink(object parameter)
+        {
+            if (parameter == null)
+            {
+                return;
+            }
+
+            string url = (string)parameter;
+            ProcessStartInfo processInfo = new ProcessStartInfo
+            {
+                FileName = url,
+                UseShellExecute = true
+            };
+            Process.Start(processInfo);
+        }
+
+        private void CenterContent(object property)
+        {
+            BitmapManager.ActiveDocument.CenterContent();
+        }
+
+        private void CloseWindow(object property)
+        {
+            if (!(property is CancelEventArgs))
+            {
+                throw new ArgumentException();
+            }
+
+            ((CancelEventArgs)property).Cancel = true;
+
+            ConfirmationType result = ConfirmationType.No;
+            if (unsavedDocumentModified)
+            {
+                result = ConfirmationDialog.Show(ConfirmationDialogMessage);
+                if (result == ConfirmationType.Yes)
+                {
+                    SaveDocument(null);
+                }
+            }
+
+            if (result != ConfirmationType.Canceled)
+            {
+                ((CancelEventArgs)property).Cancel = false;
+            }
+        }
+
+        private async void OnStartup(object parameter)
+        {
+            string lastArg = Environment.GetCommandLineArgs().Last();
+            if (Importer.IsSupportedFile(lastArg) && File.Exists(lastArg))
+            {
+                Open(lastArg);
+            }
+            else
+            {
+                OpenNewFilePopup(null);
+            }
+
+            await CheckForUpdate();
+        }
+
+        private void BitmapManager_DocumentChanged(object sender, DocumentChangedEventArgs e)
+        {
+            e.NewDocument.DocumentSizeChanged += ActiveDocument_DocumentSizeChanged;
+        }
+
+        private void Open(object property)
+        {
+            OpenFileDialog dialog = new OpenFileDialog
+            {
+                Filter = "All Files|*.*|PixiEditor Files | *.pixi|PNG Files|*.png",
+                DefaultExt = "pixi"
+            };
+            if ((bool)dialog.ShowDialog())
+            {
+                if (Importer.IsSupportedFile(dialog.FileName))
+                {
+                    Open(dialog.FileName);
+                }
+
+                RecenterZoombox = !RecenterZoombox;
+            }
+        }
+
+        private void Open(string path)
+        {
+            if (unsavedDocumentModified)
+            {
+                ConfirmationType result = ConfirmationDialog.Show(ConfirmationDialogMessage);
+                if (result == ConfirmationType.Yes)
+                {
+                    SaveDocument(null);
+                }
+                else if (result == ConfirmationType.Canceled)
+                {
+                    return;
+                }
+            }
+
+            ResetProgramStateValues();
+            if (path.EndsWith(".pixi"))
+            {
+                OpenDocument(path);
+            }
+            else
+            {
+                OpenFile(path);
+            }
+        }
+
+        private void OpenDocument(string path)
+        {
+            BitmapManager.ActiveDocument = Importer.ImportDocument(path);
+            Exporter.SaveDocumentPath = path;
+            unsavedDocumentModified = false;
+        }
+
+        private void SaveDocument(object parameter)
+        {
+            bool paramIsAsNew = parameter != null && parameter.ToString()?.ToLower() == "asnew";
+            if (paramIsAsNew || Exporter.SaveDocumentPath == null)
+            {
+                bool saved = Exporter.SaveAsEditableFileWithDialog(BitmapManager.ActiveDocument, !paramIsAsNew);
+                unsavedDocumentModified = unsavedDocumentModified && !saved;
+            }
+            else
+            {
+                Exporter.SaveAsEditableFile(BitmapManager.ActiveDocument, Exporter.SaveDocumentPath);
+                unsavedDocumentModified = false;
+            }
+        }
+
+        private void RemoveSwatch(object parameter)
+        {
+            if (!(parameter is Color))
+            {
+                throw new ArgumentException();
+            }
+
+            Color color = (Color)parameter;
+            if (BitmapManager.ActiveDocument.Swatches.Contains(color))
+            {
+                BitmapManager.ActiveDocument.Swatches.Remove(color);
+            }
+        }
+
+        private void SelectColor(object parameter)
+        {
+            PrimaryColor = parameter as Color? ?? throw new ArgumentException();
+        }
+
+        private void ActiveDocument_DocumentSizeChanged(object sender, DocumentSizeChangedEventArgs e)
+        {
+            ActiveSelection = new Selection(Array.Empty<Coordinates>());
+            RecenterZoombox = !RecenterZoombox;
+            unsavedDocumentModified = true;
+        }
+
+        private void OpenResizePopup(object parameter)
+        {
+            bool isCanvasDialog = (string)parameter == "canvas";
+            ResizeDocumentDialog dialog = new ResizeDocumentDialog(
+                BitmapManager.ActiveDocument.Width,
+                BitmapManager.ActiveDocument.Height,
+                isCanvasDialog);
+            if (dialog.ShowDialog())
+            {
+                if (isCanvasDialog)
+                {
+                    BitmapManager.ActiveDocument.ResizeCanvas(dialog.Width, dialog.Height, dialog.ResizeAnchor);
+                }
+                else
+                {
+                    BitmapManager.ActiveDocument.Resize(dialog.Width, dialog.Height);
+                }
+            }
+        }
+
+        private void DeletePixels(object parameter)
+        {
+            BitmapManager.BitmapOperations.DeletePixels(
+                new[] { BitmapManager.ActiveLayer },
+                ActiveSelection.SelectedPoints.ToArray());
+        }
+
+        private bool CanPaste(object property)
+        {
+            return DocumentIsNotNull(null) && ClipboardController.IsImageInClipboard();
+        }
+
+        private void Copy(object parameter)
+        {
+            ClipboardController.CopyToClipboard(
+                BitmapManager.ActiveDocument.Layers.ToArray(),
+                ActiveSelection.SelectedPoints.ToArray(),
+                BitmapManager.ActiveDocument.Width,
+                BitmapManager.ActiveDocument.Height);
+        }
+
+        private bool SelectionIsNotEmpty(object property)
+        {
+            return ActiveSelection?.SelectedPoints != null && ActiveSelection.SelectedPoints.Count > 0;
+        }
+
+        private bool CanSelectAll(object property)
+        {
+            return BitmapManager.ActiveDocument != null && BitmapManager.ActiveDocument.Layers.Count > 0;
+        }
+
+        private void KeyUp(object parameter)
+        {
+            KeyEventArgs args = (KeyEventArgs)parameter;
+            if (restoreToolOnKeyUp && ShortcutController.LastShortcut != null && ShortcutController.LastShortcut.ShortcutKey == args.Key)
+            {
+                restoreToolOnKeyUp = false;
+                SetActiveTool(lastActionTool);
+                ShortcutController.BlockShortcutExecution = false;
+            }
+        }
+
+        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
+        {
+            TriggerNewUndoChange(BitmapManager.SelectedTool);
+        }
+
+        private void BitmapUtility_BitmapChanged(object sender, BitmapChangedEventArgs e)
+        {
+            ChangesController.AddChanges(
+                new LayerChange(e.PixelsChanged, e.ChangedLayerIndex),
+                new LayerChange(e.OldPixelsValues, e.ChangedLayerIndex));
+            unsavedDocumentModified = true;
+            if (BitmapManager.IsOperationTool())
+            {
+                AddSwatch(PrimaryColor);
+            }
+        }
+
+        private void SetToolCursor(ToolType tool)
+        {
+            if (tool != ToolType.None)
+            {
+                ToolCursor = BitmapManager.SelectedTool.Cursor;
+            }
+            else
+            {
+                ToolCursor = Cursors.Arrow;
+            }
+        }
+
+        private void MouseDown(object parameter)
+        {
+            if (BitmapManager.ActiveDocument.Layers.Count == 0)
+            {
+                return;
+            }
+
+            if (Mouse.LeftButton == MouseButtonState.Pressed)
+            {
+                if (!BitmapManager.MouseController.IsRecordingChanges)
+                {
+                    bool clickedOnCanvas = MouseXOnCanvas >= 0 && MouseXOnCanvas <= BitmapManager.ActiveDocument.Width &&
+                                          MouseYOnCanvas >= 0 && MouseYOnCanvas <= BitmapManager.ActiveDocument.Height;
+                    BitmapManager.MouseController.StartRecordingMouseMovementChanges(clickedOnCanvas);
+                    BitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
+                }
+            }
+
+            // 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;
         }
 
         /// <summary>
-        ///     Redo last action
+        ///     Method connected with command, it executes tool "activity".
         /// </summary>
-        /// <param name="parameter"></param>
-        public void Redo(object parameter)
+        private void MouseMove(object parameter)
         {
-            UndoManager.Redo();
+            Coordinates cords = new Coordinates((int)MouseXOnCanvas, (int)MouseYOnCanvas);
+            MousePositionConverter.CurrentCoordinates = cords;
+
+            if (BitmapManager.MouseController.IsRecordingChanges && Mouse.LeftButton == MouseButtonState.Pressed)
+            {
+                BitmapManager.MouseController.RecordMouseMovementChange(cords);
+            }
+
+            BitmapManager.MouseController.MouseMoved(cords);
+        }
+
+        /// <summary>
+        ///     Returns true if undo can be done.
+        /// </summary>
+        private bool CanUndo(object property)
+        {
+            return UndoManager.CanUndo;
         }
 
         /// <summary>
         ///     Returns true if redo can be done.
         /// </summary>
-        /// <param name="property"></param>
-        /// <returns></returns>
         private bool CanRedo(object property)
         {
             return UndoManager.CanRedo;
         }
 
-        #endregion
-
-        #region SaveFile
-
         /// <summary>
         ///     Generates export dialog or saves directly if save data is known.
         /// </summary>
-        /// <param name="parameter"></param>
         private void ExportFile(object parameter)
         {
             System.Windows.Media.Imaging.WriteableBitmap bitmap = BitmapManager.GetCombinedLayersBitmap();
@@ -987,13 +983,9 @@ namespace PixiEditor.ViewModels
         /// <summary>
         ///     Returns true if file save is possible.
         /// </summary>
-        /// <param name="property"></param>
-        /// <returns></returns>
         private bool CanSave(object property)
         {
             return BitmapManager.ActiveDocument != null;
         }
-
-        #endregion
     }
 }

+ 39 - 40
PixiEditor/Views/AnchorPointPicker.xaml.cs

@@ -1,51 +1,50 @@
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Controls.Primitives;
-using PixiEditor.Models.Enums;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for AnchorPointPicker.xaml
-    /// </summary>
-    public partial class AnchorPointPicker : UserControl
-    {
-        // Using a DependencyProperty as the backing store for AnchorPoint.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty AnchorPointProperty =
-            DependencyProperty.Register("AnchorPoint", typeof(AnchorPoint), typeof(AnchorPointPicker),
-                new PropertyMetadata());
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using PixiEditor.Models.Enums;
 
-        private ToggleButton selectedToggleButton;
-
-        public AnchorPointPicker()
-        {
-            InitializeComponent();
-        }
-
-        public AnchorPoint AnchorPoint
-        {
-            get => (AnchorPoint)GetValue(AnchorPointProperty);
-            set => SetValue(AnchorPointProperty, value);
-        }
-
-        private void ToggleButton_Checked(object sender, RoutedEventArgs e)
-        {
-            ToggleButton btn = (ToggleButton)sender;
-            AnchorPoint = (AnchorPoint)(1 << (Grid.GetRow(btn) + 3)) | (AnchorPoint)(1 << Grid.GetColumn(btn));
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for AnchorPointPicker.xaml.
+    /// </summary>
+    public partial class AnchorPointPicker : UserControl
+    {
+        // Using a DependencyProperty as the backing store for AnchorPoint.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty AnchorPointProperty =
+            DependencyProperty.Register("AnchorPoint", typeof(AnchorPoint), typeof(AnchorPointPicker), new PropertyMetadata());
+
+        private ToggleButton selectedToggleButton;
+
+        public AnchorPointPicker()
+        {
+            InitializeComponent();
+        }
+
+        public AnchorPoint AnchorPoint
+        {
+            get => (AnchorPoint)GetValue(AnchorPointProperty);
+            set => SetValue(AnchorPointProperty, value);
+        }
+
+        private void ToggleButton_Checked(object sender, RoutedEventArgs e)
+        {
+            ToggleButton btn = (ToggleButton)sender;
+            AnchorPoint = (AnchorPoint)(1 << (Grid.GetRow(btn) + 3)) | (AnchorPoint)(1 << Grid.GetColumn(btn));
             if (selectedToggleButton != null)
             {
                 selectedToggleButton.IsChecked = false;
             }
 
-            selectedToggleButton = btn;
-        }
-
-        private void ToggleButton_Click(object sender, RoutedEventArgs e)
-        {
+            selectedToggleButton = btn;
+        }
+
+        private void ToggleButton_Click(object sender, RoutedEventArgs e)
+        {
             if ((sender as ToggleButton).IsChecked.Value)
             {
                 e.Handled = true;
             }
-        }
-    }
+        }
+    }
 }

+ 60 - 57
PixiEditor/Views/ConfirmationPopup.xaml.cs

@@ -1,58 +1,61 @@
-using System.Windows;
-using PixiEditor.Helpers;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for ConfirmationPopup.xaml
-    /// </summary>
-    public partial class ConfirmationPopup : Window
-    {
-        // Using a DependencyProperty as the backing store for SaveChanges.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty SaveChangesProperty =
-            DependencyProperty.Register("SaveChanges", typeof(bool), typeof(ConfirmationPopup),
-                new PropertyMetadata(true));
-
-        // Using a DependencyProperty as the backing store for Body.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty BodyProperty =
-            DependencyProperty.Register("Body", typeof(string), typeof(ConfirmationPopup), new PropertyMetadata(""));
-
-        public ConfirmationPopup()
-        {
-            InitializeComponent();
-            CancelCommand = new RelayCommand(Cancel);
-            SetResultAndCloseCommand = new RelayCommand(SetResultAndClose);
-            DataContext = this;
-        }
-
-        public RelayCommand CancelCommand { get; set; }
-
-        public RelayCommand SetResultAndCloseCommand { get; set; }
-
-        public bool Result
-        {
-            get => (bool)GetValue(SaveChangesProperty);
-            set => SetValue(SaveChangesProperty, value);
-        }
-
-        public string Body
-        {
-            get => (string)GetValue(BodyProperty);
-            set => SetValue(BodyProperty, value);
-        }
-
-        private void SetResultAndClose(object property)
-        {
-            bool result = (bool)property;
-            Result = result;
-            DialogResult = true;
-            Close();
-        }
-
-        private void Cancel(object property)
-        {
-            DialogResult = false;
-            Close();
-        }
-    }
+using System.Windows;
+using PixiEditor.Helpers;
+
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for ConfirmationPopup.xaml.
+    /// </summary>
+    public partial class ConfirmationPopup : Window
+    {
+        // Using a DependencyProperty as the backing store for SaveChanges.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty SaveChangesProperty =
+            DependencyProperty.Register(
+                "SaveChanges",
+                typeof(bool),
+                typeof(ConfirmationPopup),
+                new PropertyMetadata(true));
+
+        // Using a DependencyProperty as the backing store for Body.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty BodyProperty =
+            DependencyProperty.Register("Body", typeof(string), typeof(ConfirmationPopup), new PropertyMetadata(string.Empty));
+
+        public ConfirmationPopup()
+        {
+            InitializeComponent();
+            CancelCommand = new RelayCommand(Cancel);
+            SetResultAndCloseCommand = new RelayCommand(SetResultAndClose);
+            DataContext = this;
+        }
+
+        public RelayCommand CancelCommand { get; set; }
+
+        public RelayCommand SetResultAndCloseCommand { get; set; }
+
+        public bool Result
+        {
+            get => (bool)GetValue(SaveChangesProperty);
+            set => SetValue(SaveChangesProperty, value);
+        }
+
+        public string Body
+        {
+            get => (string)GetValue(BodyProperty);
+            set => SetValue(BodyProperty, value);
+        }
+
+        private void SetResultAndClose(object property)
+        {
+            bool result = (bool)property;
+            Result = result;
+            DialogResult = true;
+            Close();
+        }
+
+        private void Cancel(object property)
+        {
+            DialogResult = false;
+            Close();
+        }
+    }
 }

+ 1 - 1
PixiEditor/Views/EditableTextBlock.xaml

@@ -17,7 +17,7 @@
                  LostFocus="TextBox_LostFocus"
                  Text="{Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
                  KeyDown="TextBox_KeyDown"
-                 LostKeyboardFocus="textBox_LostKeyboardFocus"
+                 LostKeyboardFocus="TextBox_LostKeyboardFocus"
                  Visibility="{Binding Path=TextBlockVisibility, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, 
             Converter={StaticResource OppositeVisibilityConverter}}"
                  Name="textBox" />

+ 14 - 17
PixiEditor/Views/EditableTextBlock.xaml.cs

@@ -6,24 +6,21 @@ using PixiEditor.Models.Controllers.Shortcuts;
 namespace PixiEditor.Views
 {
     /// <summary>
-    ///     Interaction logic for EditableTextBlock.xaml
+    ///     Interaction logic for EditableTextBlock.xaml.
     /// </summary>
     public partial class EditableTextBlock : UserControl
     {
         // Using a DependencyProperty as the backing store for TextBlockVisibility.  This enables animation, styling, binding, etc...
         public static readonly DependencyProperty TextBlockVisibilityProperty =
-            DependencyProperty.Register("TextBlockVisibility", typeof(Visibility), typeof(EditableTextBlock),
-                new PropertyMetadata(Visibility.Visible));
+            DependencyProperty.Register("TextBlockVisibility", typeof(Visibility), typeof(EditableTextBlock), new PropertyMetadata(Visibility.Visible));
 
         // Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...
         public static readonly DependencyProperty TextProperty =
-            DependencyProperty.Register("Text", typeof(string), typeof(EditableTextBlock),
-                new PropertyMetadata(default(string)));
+            DependencyProperty.Register("Text", typeof(string), typeof(EditableTextBlock), new PropertyMetadata(default(string)));
 
         // Using a DependencyProperty as the backing store for EnableEditing.  This enables animation, styling, binding, etc...
         public static readonly DependencyProperty EnableEditingProperty =
-            DependencyProperty.Register("IsEditing", typeof(bool), typeof(EditableTextBlock),
-                new PropertyMetadata(OnIsEditingChanged));
+            DependencyProperty.Register("IsEditing", typeof(bool), typeof(EditableTextBlock), new PropertyMetadata(OnIsEditingChanged));
 
         public EditableTextBlock()
         {
@@ -48,15 +45,6 @@ namespace PixiEditor.Views
             set => SetValue(TextProperty, value);
         }
 
-        private static void OnIsEditingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            if ((bool)e.NewValue)
-            {
-                EditableTextBlock tb = (EditableTextBlock)d;
-                tb.EnableEditing();
-            }
-        }
-
         public void EnableEditing()
         {
             ShortcutController.BlockShortcutExecution = true;
@@ -66,6 +54,15 @@ namespace PixiEditor.Views
             textBox.SelectAll();
         }
 
+        private static void OnIsEditingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            if ((bool)e.NewValue)
+            {
+                EditableTextBlock tb = (EditableTextBlock)d;
+                tb.EnableEditing();
+            }
+        }
+
         private void DisableEditing()
         {
             TextBlockVisibility = Visibility.Visible;
@@ -94,7 +91,7 @@ namespace PixiEditor.Views
             DisableEditing();
         }
 
-        private void textBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
+        private void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
         {
             DisableEditing();
         }

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

@@ -4,7 +4,7 @@ using PixiEditor.ViewModels;
 namespace PixiEditor.Views
 {
     /// <summary>
-    ///     Interaction logic for ImportFilePopup.xaml
+    ///     Interaction logic for ImportFilePopup.xaml.
     /// </summary>
     public partial class ImportFilePopup : Window
     {

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

@@ -6,7 +6,7 @@ using PixiEditor.Helpers;
 namespace PixiEditor.Views
 {
     /// <summary>
-    ///     Interaction logic for LayerItem.xaml
+    ///     Interaction logic for LayerItem.xaml.
     /// </summary>
     public partial class LayerItem : UserControl
     {

+ 1 - 1
PixiEditor/Views/MainDrawingPanel.xaml

@@ -5,7 +5,7 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
              xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
-             mc:Ignorable="d" PreviewMouseDown="mainDrawingPanel_MouseDown" PreviewMouseUp="mainDrawingPanel_PreviewMouseUp"
+             mc:Ignorable="d" PreviewMouseDown="MainDrawingPanel_MouseDown" PreviewMouseUp="MainDrawingPanel_PreviewMouseUp"
              d:DesignHeight="450" d:DesignWidth="800" x:Name="mainDrawingPanel" PreviewMouseWheel="Zoombox_MouseWheel">
     <xctk:Zoombox PreviewMouseDown="Zoombox_PreviewMouseDown" Cursor="{Binding Cursor}" Name="Zoombox" KeepContentInBounds="True"
                   Loaded="Zoombox_Loaded" IsAnimated="False" CurrentViewChanged="Zoombox_CurrentViewChanged" DragModifiers="Shift" ZoomModifiers="None">

+ 162 - 165
PixiEditor/Views/MainDrawingPanel.xaml.cs

@@ -1,190 +1,187 @@
-using System;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using PixiEditor.Models.Tools.Tools;
-using PixiEditor.ViewModels;
-using Xceed.Wpf.Toolkit.Core.Input;
-using Xceed.Wpf.Toolkit.Zoombox;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for MainDrawingPanel.xaml
-    /// </summary>
-    public partial class MainDrawingPanel : UserControl
-    {
-        // Using a DependencyProperty as the backing store for Center.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty CenterProperty =
-            DependencyProperty.Register("Center", typeof(bool), typeof(MainDrawingPanel),
-                new PropertyMetadata(true, OnCenterChanged));
-
-        // Using a DependencyProperty as the backing store for MouseX.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty MouseXProperty =
-            DependencyProperty.Register("MouseX", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(null));
-
-        // Using a DependencyProperty as the backing store for MouseX.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty MouseYProperty =
-            DependencyProperty.Register("MouseY", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(null));
-
-        // Using a DependencyProperty as the backing store for MouseMoveCommand.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty MouseMoveCommandProperty =
-            DependencyProperty.Register("MouseMoveCommand", typeof(ICommand), typeof(MainDrawingPanel),
-                new PropertyMetadata(null));
-
-        // Using a DependencyProperty as the backing store for CenterOnStart.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty CenterOnStartProperty =
-            DependencyProperty.Register("CenterOnStart", typeof(bool), typeof(MainDrawingPanel),
-                new PropertyMetadata(false));
-
-        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty ItemProperty =
-            DependencyProperty.Register("Item", typeof(object), typeof(MainDrawingPanel), new PropertyMetadata(default(FrameworkElement)));
-
-        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty IsUsingZoomToolProperty =
-            DependencyProperty.Register("IsUsingZoomTool", typeof(bool), typeof(MainDrawingPanel), new PropertyMetadata(false));
-
-        // Using a DependencyProperty as the backing store for ZoomPercentage.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty ZoomPercentageProperty =
-            DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(0.0, ZoomPercentegeChanged));
-
-        public double ClickScale;
-
-        public MainDrawingPanel()
-        {
-            InitializeComponent();
-            Zoombox.ZoomToSelectionModifiers = new KeyModifierCollection { KeyModifier.RightAlt };
-        }
-
-        public double ZoomPercentage
-        {
-            get => (double)GetValue(ZoomPercentageProperty);
-            set => SetValue(ZoomPercentageProperty, value);
-        }
-
-        public bool Center
-        {
-            get => (bool)GetValue(CenterProperty);
-            set => SetValue(CenterProperty, value);
-        }
-
-        public double MouseX
-        {
-            get => (double)GetValue(MouseXProperty);
-            set => SetValue(MouseXProperty, value);
-        }
-
-        public double MouseY
-        {
-            get => (double)GetValue(MouseYProperty);
-            set => SetValue(MouseYProperty, value);
-        }
-
-        public ICommand MouseMoveCommand
-        {
-            get => (ICommand)GetValue(MouseMoveCommandProperty);
-            set => SetValue(MouseMoveCommandProperty, value);
-        }
-
-        public bool CenterOnStart
-        {
-            get => (bool)GetValue(CenterOnStartProperty);
-            set => SetValue(CenterOnStartProperty, value);
-        }
-
-        public object Item
-        {
-            get => GetValue(ItemProperty);
-            set => SetValue(ItemProperty, value);
-        }
-
-        public bool IsUsingZoomTool
-        {
-            get => (bool)GetValue(IsUsingZoomToolProperty);
-            set => SetValue(IsUsingZoomToolProperty, value);
-        }
-
-        private static void ZoomPercentegeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            MainDrawingPanel panel = (MainDrawingPanel)d;
-            double percentage = (double)e.NewValue;
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using PixiEditor.Models.Tools.Tools;
+using PixiEditor.ViewModels;
+using Xceed.Wpf.Toolkit.Core.Input;
+using Xceed.Wpf.Toolkit.Zoombox;
+
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for MainDrawingPanel.xaml.
+    /// </summary>
+    public partial class MainDrawingPanel : UserControl
+    {
+        // Using a DependencyProperty as the backing store for Center.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty CenterProperty =
+            DependencyProperty.Register("Center", typeof(bool), typeof(MainDrawingPanel), new PropertyMetadata(true, OnCenterChanged));
+
+        // Using a DependencyProperty as the backing store for MouseX.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseXProperty =
+            DependencyProperty.Register("MouseX", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(null));
+
+        // Using a DependencyProperty as the backing store for MouseX.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseYProperty =
+            DependencyProperty.Register("MouseY", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(null));
+
+        // Using a DependencyProperty as the backing store for MouseMoveCommand.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseMoveCommandProperty =
+            DependencyProperty.Register("MouseMoveCommand", typeof(ICommand), typeof(MainDrawingPanel), new PropertyMetadata(null));
+
+        // Using a DependencyProperty as the backing store for CenterOnStart.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty CenterOnStartProperty =
+            DependencyProperty.Register("CenterOnStart", typeof(bool), typeof(MainDrawingPanel), new PropertyMetadata(false));
+
+        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ItemProperty =
+            DependencyProperty.Register("Item", typeof(object), typeof(MainDrawingPanel), new PropertyMetadata(default(FrameworkElement)));
+
+        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty IsUsingZoomToolProperty =
+            DependencyProperty.Register("IsUsingZoomTool", typeof(bool), typeof(MainDrawingPanel), new PropertyMetadata(false));
+
+        // Using a DependencyProperty as the backing store for ZoomPercentage.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ZoomPercentageProperty =
+            DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MainDrawingPanel), new PropertyMetadata(0.0, ZoomPercentegeChanged));
+
+        public MainDrawingPanel()
+        {
+            InitializeComponent();
+            Zoombox.ZoomToSelectionModifiers = new KeyModifierCollection { KeyModifier.RightAlt };
+        }
+
+        public double ClickScale { get; set; }
+
+        public double ZoomPercentage
+        {
+            get => (double)GetValue(ZoomPercentageProperty);
+            set => SetValue(ZoomPercentageProperty, value);
+        }
+
+        public bool Center
+        {
+            get => (bool)GetValue(CenterProperty);
+            set => SetValue(CenterProperty, value);
+        }
+
+        public double MouseX
+        {
+            get => (double)GetValue(MouseXProperty);
+            set => SetValue(MouseXProperty, value);
+        }
+
+        public double MouseY
+        {
+            get => (double)GetValue(MouseYProperty);
+            set => SetValue(MouseYProperty, value);
+        }
+
+        public ICommand MouseMoveCommand
+        {
+            get => (ICommand)GetValue(MouseMoveCommandProperty);
+            set => SetValue(MouseMoveCommandProperty, value);
+        }
+
+        public bool CenterOnStart
+        {
+            get => (bool)GetValue(CenterOnStartProperty);
+            set => SetValue(CenterOnStartProperty, value);
+        }
+
+        public object Item
+        {
+            get => GetValue(ItemProperty);
+            set => SetValue(ItemProperty, value);
+        }
+
+        public bool IsUsingZoomTool
+        {
+            get => (bool)GetValue(IsUsingZoomToolProperty);
+            set => SetValue(IsUsingZoomToolProperty, value);
+        }
+
+        private static void OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            MainDrawingPanel panel = (MainDrawingPanel)d;
+            panel.Zoombox.CenterContent();
+        }
+
+        private static void ZoomPercentegeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            MainDrawingPanel panel = (MainDrawingPanel)d;
+            double percentage = (double)e.NewValue;
             if (percentage == 100)
             {
                 panel.SetClickValues();
             }
 
-            panel.Zoombox.ZoomTo(panel.ClickScale * ((double)e.NewValue / 100.0));
-        }
-
-        private void Zoombox_CurrentViewChanged(object sender, ZoomboxViewChangedEventArgs e)
-        {
-            Zoombox.MinScale = 32 / ((FrameworkElement)Item).Width;
-            Zoombox.KeepContentInBounds = !(Zoombox.Scale > Zoombox.MinScale * 35.0);
-        }
-
-        private void Zoombox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
-        {
+            panel.Zoombox.ZoomTo(panel.ClickScale * ((double)e.NewValue / 100.0));
+        }
+
+        private void Zoombox_CurrentViewChanged(object sender, ZoomboxViewChangedEventArgs e)
+        {
+            Zoombox.MinScale = 32 / ((FrameworkElement)Item).Width;
+            Zoombox.KeepContentInBounds = !(Zoombox.Scale > Zoombox.MinScale * 35.0);
+        }
+
+        private void Zoombox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
+        {
             if (ZoomPercentage == 100)
             {
                 SetClickValues();
             }
-        }
-
-        private void SetClickValues()
-        {
+        }
+
+        private void SetClickValues()
+        {
             if (!IsUsingZoomTool)
             {
                 return;
             }
 
-            ClickScale = Zoombox.Scale;
-            SetZoomOrigin();
-        }
-
-        private void SetZoomOrigin()
-        {
-            FrameworkElement item = (FrameworkElement)Item;
+            ClickScale = Zoombox.Scale;
+            SetZoomOrigin();
+        }
+
+        private void SetZoomOrigin()
+        {
+            FrameworkElement item = (FrameworkElement)Item;
             if (item == null)
             {
                 return;
             }
 
-            Point mousePos = Mouse.GetPosition(item);
-            Zoombox.ZoomOrigin = new Point(Math.Clamp(mousePos.X / item.Width, 0, 1), Math.Clamp(mousePos.Y / item.Height, 0, 1));
-        }
-
-        private static void OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            MainDrawingPanel panel = (MainDrawingPanel)d;
-            panel.Zoombox.CenterContent();
-        }
-
-        private void Zoombox_Loaded(object sender, RoutedEventArgs e)
-        {
+            Point mousePos = Mouse.GetPosition(item);
+            Zoombox.ZoomOrigin = new Point(Math.Clamp(mousePos.X / item.Width, 0, 1), Math.Clamp(mousePos.Y / item.Height, 0, 1));
+        }
+
+        private void Zoombox_Loaded(object sender, RoutedEventArgs e)
+        {
             if (CenterOnStart)
             {
                 ((Zoombox)sender).CenterContent();
             }
 
-            ClickScale = Zoombox.Scale;
-        }
-
-        private void Zoombox_MouseWheel(object sender, MouseWheelEventArgs e)
-        {
-            SetZoomOrigin();
-        }
-
-        private void mainDrawingPanel_MouseDown(object sender, MouseButtonEventArgs e)
-        {
-            IsUsingZoomTool = ViewModelMain.Current.BitmapManager.SelectedTool is ZoomTool;
-            Mouse.Capture((IInputElement)sender, CaptureMode.SubTree);
-            SetClickValues();
-        }
-
-        private void mainDrawingPanel_PreviewMouseUp(object sender, MouseButtonEventArgs e)
-        {
-            ((IInputElement)sender).ReleaseMouseCapture();
-        }
-    }
+            ClickScale = Zoombox.Scale;
+        }
+
+        private void Zoombox_MouseWheel(object sender, MouseWheelEventArgs e)
+        {
+            SetZoomOrigin();
+        }
+
+        private void MainDrawingPanel_MouseDown(object sender, MouseButtonEventArgs e)
+        {
+            IsUsingZoomTool = ViewModelMain.Current.BitmapManager.SelectedTool is ZoomTool;
+            Mouse.Capture((IInputElement)sender, CaptureMode.SubTree);
+            SetClickValues();
+        }
+
+        private void MainDrawingPanel_PreviewMouseUp(object sender, MouseButtonEventArgs e)
+        {
+            ((IInputElement)sender).ReleaseMouseCapture();
+        }
+    }
 }

+ 1 - 1
PixiEditor/Views/MainWindow.xaml

@@ -12,7 +12,7 @@
         xmlns:cmd="http://www.galasoft.ch/mvvmlight"
         xmlns:avalondock="https://github.com/Dirkster99/AvalonDock"
         xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker"
-        mc:Ignorable="d" WindowStyle="None" Initialized="mainWindow_Initialized"
+        mc:Ignorable="d" WindowStyle="None" Initialized="MainWindow_Initialized"
         Title="PixiEditor" Name="mainWindow" Height="1000" Width="1600" Background="{StaticResource MainColor}"
         WindowStartupLocation="CenterScreen" WindowState="Maximized" DataContext="{DynamicResource ViewModelMain}">
     <WindowChrome.WindowChrome>

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

@@ -8,7 +8,7 @@ using PixiEditor.ViewModels;
 namespace PixiEditor
 {
     /// <summary>
-    ///     Interaction logic for MainWindow.xaml
+    ///     Interaction logic for MainWindow.xaml.
     /// </summary>
     public partial class MainWindow : Window
     {
@@ -62,7 +62,7 @@ namespace PixiEditor
             }
         }
 
-        private void mainWindow_Initialized(object sender, EventArgs e)
+        private void MainWindow_Initialized(object sender, EventArgs e)
         {
             string dir = AppDomain.CurrentDomain.BaseDirectory;
             bool updateFileExists = Directory.GetFiles(dir, "update-*.zip").Length > 0;

+ 42 - 43
PixiEditor/Views/MenuButton.xaml.cs

@@ -1,45 +1,44 @@
-using System.Windows;
-using System.Windows.Controls;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for MenuButton.xaml
-    /// </summary>
-    public partial class MenuButton : UserControl
-    {
-        public static readonly DependencyProperty MenuButtonTextProperty =
-            DependencyProperty.Register("Text", typeof(string), typeof(MenuButton),
-                new UIPropertyMetadata(string.Empty));
-
-        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty ItemProperty =
-            DependencyProperty.Register("Item", typeof(object), typeof(MenuButton), new PropertyMetadata(null));
-
-        private readonly MenuButtonViewModel dc = new MenuButtonViewModel();
-
-        public MenuButton()
-        {
-            InitializeComponent();
-            DataContext = dc;
-        }
-
-        public string Text
-        {
-            get => (string)GetValue(MenuButtonTextProperty);
-            set => SetValue(MenuButtonTextProperty, value);
-        }
+using System.Windows;
+using System.Windows.Controls;
+using PixiEditor.ViewModels;
 
-        public object Item
-        {
-            get => GetValue(ItemProperty);
-            set => SetValue(ItemProperty, value);
-        }
-
-        protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
-        {
-            dc.CloseListViewCommand.Execute(null);
-        }
-    }
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for MenuButton.xaml.
+    /// </summary>
+    public partial class MenuButton : UserControl
+    {
+        public static readonly DependencyProperty MenuButtonTextProperty =
+            DependencyProperty.Register("Text", typeof(string), typeof(MenuButton), new UIPropertyMetadata(string.Empty));
+
+        // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ItemProperty =
+            DependencyProperty.Register("Item", typeof(object), typeof(MenuButton), new PropertyMetadata(null));
+
+        private readonly MenuButtonViewModel dc = new MenuButtonViewModel();
+
+        public MenuButton()
+        {
+            InitializeComponent();
+            DataContext = dc;
+        }
+
+        public string Text
+        {
+            get => (string)GetValue(MenuButtonTextProperty);
+            set => SetValue(MenuButtonTextProperty, value);
+        }
+
+        public object Item
+        {
+            get => GetValue(ItemProperty);
+            set => SetValue(ItemProperty, value);
+        }
+
+        protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
+        {
+            dc.CloseListViewCommand.Execute(null);
+        }
+    }
 }

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

@@ -3,7 +3,7 @@
 namespace PixiEditor.Views
 {
     /// <summary>
-    ///     Interaction logic for NewFilePopup.xaml
+    ///     Interaction logic for NewFilePopup.xaml.
     /// </summary>
     public partial class NewFilePopup : Window
     {

+ 70 - 61
PixiEditor/Views/NumberInput.xaml.cs

@@ -1,64 +1,73 @@
-using System;
-using System.Text.RegularExpressions;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for NumerInput.xaml
-    /// </summary>
-    public partial class NumberInput : UserControl
-    {
-        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty ValueProperty =
-            DependencyProperty.Register("Value", typeof(float), typeof(NumberInput),
-                new PropertyMetadata(0f, OnValueChanged));
-
-        // Using a DependencyProperty as the backing store for Min.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty MinProperty =
-            DependencyProperty.Register("Min", typeof(float), typeof(NumberInput),
-                new PropertyMetadata(float.NegativeInfinity));
-
-        // Using a DependencyProperty as the backing store for Max.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty MaxProperty =
-            DependencyProperty.Register("Max", typeof(float), typeof(NumberInput),
-                new PropertyMetadata(float.PositiveInfinity));
+using System;
+using System.Text.RegularExpressions;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
 
-        public NumberInput()
-        {
-            InitializeComponent();
-        }
-
-        public float Value
-        {
-            get => (float)GetValue(ValueProperty);
-            set => SetValue(ValueProperty, value);
-        }
-
-        public float Min
-        {
-            get => (float)GetValue(MinProperty);
-            set => SetValue(MinProperty, value);
-        }
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for NumerInput.xaml.
+    /// </summary>
+    public partial class NumberInput : UserControl
+    {
+        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ValueProperty =
+            DependencyProperty.Register(
+                "Value",
+                typeof(float),
+                typeof(NumberInput),
+                new PropertyMetadata(0f, OnValueChanged));
 
-        public float Max
-        {
-            get => (float)GetValue(MaxProperty);
-            set => SetValue(MaxProperty, value);
-        }
-
-        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            NumberInput input = (NumberInput)d;
-            input.Value = Math.Clamp((float)e.NewValue, input.Min, input.Max);
-        }
-
-        private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
-        {
-            Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$");
-            e.Handled = !regex.IsMatch((sender as TextBox).Text.Insert((sender as TextBox).SelectionStart, e.Text));
-        }
-    }
+        // Using a DependencyProperty as the backing store for Min.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MinProperty =
+            DependencyProperty.Register(
+                "Min",
+                typeof(float),
+                typeof(NumberInput),
+                new PropertyMetadata(float.NegativeInfinity));
+
+        // Using a DependencyProperty as the backing store for Max.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MaxProperty =
+            DependencyProperty.Register(
+                "Max",
+                typeof(float),
+                typeof(NumberInput),
+                new PropertyMetadata(float.PositiveInfinity));
+
+        public NumberInput()
+        {
+            InitializeComponent();
+        }
+
+        public float Value
+        {
+            get => (float)GetValue(ValueProperty);
+            set => SetValue(ValueProperty, value);
+        }
+
+        public float Min
+        {
+            get => (float)GetValue(MinProperty);
+            set => SetValue(MinProperty, value);
+        }
+
+        public float Max
+        {
+            get => (float)GetValue(MaxProperty);
+            set => SetValue(MaxProperty, value);
+        }
+
+        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            NumberInput input = (NumberInput)d;
+            input.Value = Math.Clamp((float)e.NewValue, input.Min, input.Max);
+        }
+
+        private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
+        {
+            Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$");
+            e.Handled = !regex.IsMatch((sender as TextBox).Text.Insert((sender as TextBox).SelectionStart, e.Text));
+        }
+    }
 }

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

@@ -4,7 +4,7 @@ using System.Windows.Input;
 namespace PixiEditor.Views
 {
     /// <summary>
-    ///     Interaction logic for PopupTemplate.xaml
+    ///     Interaction logic for PopupTemplate.xaml.
     /// </summary>
     public partial class PopupTemplate : Window
     {

+ 62 - 59
PixiEditor/Views/ResizeCanvasPopup.xaml.cs

@@ -1,64 +1,67 @@
-using System.Windows;
-using System.Windows.Input;
-using PixiEditor.Models.Enums;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for ResizeCanvasPopup.xaml
-    /// </summary>
-    public partial class ResizeCanvasPopup : Window
-    {
-        // Using a DependencyProperty as the backing store for SelectedAnchorPoint.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty SelectedAnchorPointProperty =
-            DependencyProperty.Register("SelectedAnchorPoint", typeof(AnchorPoint), typeof(ResizeCanvasPopup),
-                new PropertyMetadata(AnchorPoint.Top | AnchorPoint.Left));
-
-        // Using a DependencyProperty as the backing store for NewHeight.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty NewHeightProperty =
-            DependencyProperty.Register("NewHeight", typeof(int), typeof(ResizeCanvasPopup), new PropertyMetadata(0));
-
-        // Using a DependencyProperty as the backing store for NewWidth.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty NewWidthProperty =
-            DependencyProperty.Register("NewWidth", typeof(int), typeof(ResizeCanvasPopup), new PropertyMetadata(0));
-
-        public ResizeCanvasPopup()
-        {
-            InitializeComponent();
-        }
+using System.Windows;
+using System.Windows.Input;
+using PixiEditor.Models.Enums;
 
-        public AnchorPoint SelectedAnchorPoint
-        {
-            get => (AnchorPoint)GetValue(SelectedAnchorPointProperty);
-            set => SetValue(SelectedAnchorPointProperty, value);
-        }
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for ResizeCanvasPopup.xaml.
+    /// </summary>
+    public partial class ResizeCanvasPopup : Window
+    {
+        // Using a DependencyProperty as the backing store for SelectedAnchorPoint.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty SelectedAnchorPointProperty =
+            DependencyProperty.Register(
+                "SelectedAnchorPoint",
+                typeof(AnchorPoint),
+                typeof(ResizeCanvasPopup),
+                new PropertyMetadata(AnchorPoint.Top | AnchorPoint.Left));
 
-        public int NewHeight
-        {
-            get => (int)GetValue(NewHeightProperty);
-            set => SetValue(NewHeightProperty, value);
-        }
+        // Using a DependencyProperty as the backing store for NewHeight.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty NewHeightProperty =
+            DependencyProperty.Register("NewHeight", typeof(int), typeof(ResizeCanvasPopup), new PropertyMetadata(0));
 
-        public int NewWidth
-        {
-            get => (int)GetValue(NewWidthProperty);
-            set => SetValue(NewWidthProperty, value);
-        }
+        // Using a DependencyProperty as the backing store for NewWidth.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty NewWidthProperty =
+            DependencyProperty.Register("NewWidth", typeof(int), typeof(ResizeCanvasPopup), new PropertyMetadata(0));
 
-        private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
-        {
-            e.CanExecute = true;
-        }
-
-        private void CommandBinding_Executed_Close(object sender, ExecutedRoutedEventArgs e)
-        {
-            SystemCommands.CloseWindow(this);
-        }
-
-        private void Button_Click(object sender, RoutedEventArgs e)
-        {
-            DialogResult = true;
-            Close();
-        }
-    }
+        public ResizeCanvasPopup()
+        {
+            InitializeComponent();
+        }
+
+        public AnchorPoint SelectedAnchorPoint
+        {
+            get => (AnchorPoint)GetValue(SelectedAnchorPointProperty);
+            set => SetValue(SelectedAnchorPointProperty, value);
+        }
+
+        public int NewHeight
+        {
+            get => (int)GetValue(NewHeightProperty);
+            set => SetValue(NewHeightProperty, value);
+        }
+
+        public int NewWidth
+        {
+            get => (int)GetValue(NewWidthProperty);
+            set => SetValue(NewWidthProperty, value);
+        }
+
+        private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
+        {
+            e.CanExecute = true;
+        }
+
+        private void CommandBinding_Executed_Close(object sender, ExecutedRoutedEventArgs e)
+        {
+            SystemCommands.CloseWindow(this);
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            DialogResult = true;
+            Close();
+        }
+    }
 }

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

@@ -4,7 +4,7 @@ using System.Windows.Input;
 namespace PixiEditor.Views
 {
     /// <summary>
-    ///     Interaction logic for ResizeDocumentPopup.xaml
+    ///     Interaction logic for ResizeDocumentPopup.xaml.
     /// </summary>
     public partial class ResizeDocumentPopup : Window
     {

+ 66 - 64
PixiEditor/Views/Rotatebox.xaml.cs

@@ -1,70 +1,72 @@
-using System;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for Rotatebox.xaml
-    /// </summary>
-    public partial class Rotatebox : UserControl
-    {
-        // Using a DependencyProperty backing store for Angle.
-        public static readonly DependencyProperty AngleProperty =
-            DependencyProperty.Register("Angle", typeof(double), typeof(Rotatebox), new UIPropertyMetadata(0.0));
-
-        private double height, width;
-        private readonly float offset = 90;
-
-        public Rotatebox()
-        {
-            InitializeComponent();
-            MouseLeftButtonDown += OnMouseLeftButtonDown;
-            MouseUp += OnMouseUp;
-            MouseMove += OnMouseMove;
-        }
-
-        public double Angle
-        {
-            get => (double)GetValue(AngleProperty);
-            set => SetValue(AngleProperty, value);
-        }
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
 
-        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
-        {
-            Mouse.Capture(this);
-            width = ActualWidth;
-            height = ActualHeight;
-        }
-
-        private void OnMouseUp(object sender, MouseButtonEventArgs e)
-        {
-            Mouse.Capture(null);
-        }
-
-        private void OnMouseMove(object sender, MouseEventArgs e)
-        {
-            if (Equals(Mouse.Captured, this))
-            {
-                // Get the current mouse position relative to the control
-                Point currentLocation = Mouse.GetPosition(this);
-
-                // We want to rotate around the center of the knob, not the top corner
-                Point knobCenter = new Point(width / 2, height / 2);
-
-                // Calculate an angle
-                double radians = Math.Atan((currentLocation.Y - knobCenter.Y) /
-                                        (currentLocation.X - knobCenter.X));
-                Angle = radians * 180 / Math.PI + offset;
-
-                // Apply a 180 degree shift when X is negative so that we can rotate
-                // all of the way around
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for Rotatebox.xaml.
+    /// </summary>
+    public partial class Rotatebox : UserControl
+    {
+        // Using a DependencyProperty backing store for Angle.
+        public static readonly DependencyProperty AngleProperty =
+            DependencyProperty.Register("Angle", typeof(double), typeof(Rotatebox), new UIPropertyMetadata(0.0));
+
+        private readonly float offset = 90;
+
+        private double height;
+        private double width;
+
+        public Rotatebox()
+        {
+            InitializeComponent();
+            MouseLeftButtonDown += OnMouseLeftButtonDown;
+            MouseUp += OnMouseUp;
+            MouseMove += OnMouseMove;
+        }
+
+        public double Angle
+        {
+            get => (double)GetValue(AngleProperty);
+            set => SetValue(AngleProperty, value);
+        }
+
+        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            Mouse.Capture(this);
+            width = ActualWidth;
+            height = ActualHeight;
+        }
+
+        private void OnMouseUp(object sender, MouseButtonEventArgs e)
+        {
+            Mouse.Capture(null);
+        }
+
+        private void OnMouseMove(object sender, MouseEventArgs e)
+        {
+            if (Equals(Mouse.Captured, this))
+            {
+                // Get the current mouse position relative to the control
+                Point currentLocation = Mouse.GetPosition(this);
+
+                // We want to rotate around the center of the knob, not the top corner
+                Point knobCenter = new Point(width / 2, height / 2);
+
+                // Calculate an angle
+                double radians = Math.Atan((currentLocation.Y - knobCenter.Y) /
+                                        (currentLocation.X - knobCenter.X));
+                Angle = (radians * 180 / Math.PI) + offset;
+
+                // Apply a 180 degree shift when X is negative so that we can rotate
+                // all of the way around
                 if (currentLocation.X - knobCenter.X < 0)
                 {
                     Angle += 180;
                 }
-            }
-        }
-    }
+            }
+        }
+    }
 }

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

@@ -4,7 +4,7 @@ using PixiEditor.ViewModels;
 namespace PixiEditor.Views
 {
     /// <summary>
-    ///     Interaction logic for SaveFilePopup.xaml
+    ///     Interaction logic for SaveFilePopup.xaml.
     /// </summary>
     public partial class SaveFilePopup : Window
     {

+ 1 - 1
PixiEditor/Views/SizeInput.xaml

@@ -8,7 +8,7 @@
              xmlns:converters="clr-namespace:PixiEditor.Helpers"
              xmlns:validators="clr-namespace:PixiEditor.Helpers.Validators"
              mc:Ignorable="d"
-             d:DesignHeight="30" d:DesignWidth="160" Name="uc" LayoutUpdated="uc_LayoutUpdated">
+             d:DesignHeight="30" d:DesignWidth="160" Name="uc" LayoutUpdated="Uc_LayoutUpdated">
     <UserControl.Resources>
         <converters:ToolSizeToIntConverter x:Key="ToolSizeToIntConverter" />
     </UserControl.Resources>

+ 69 - 71
PixiEditor/Views/SizeInput.xaml.cs

@@ -1,74 +1,72 @@
-using System;
-using System.Windows;
-using System.Windows.Controls;
-
-namespace PixiEditor.Views
-{
-    /// <summary>
-    ///     Interaction logic for SizeInput.xaml
-    /// </summary>
-    public partial class SizeInput : UserControl
-    {
-        // Using a DependencyProperty as the backing store for Size.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty SizeProperty =
-            DependencyProperty.Register("Size", typeof(int), typeof(SizeInput), new PropertyMetadata(1));
-
-        // Using a DependencyProperty as the backing store for PreserveAspectRatio.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty PreserveAspectRatioProperty =
-            DependencyProperty.Register("PreserveAspectRatio", typeof(bool), typeof(SizeInput),
-                new PropertyMetadata(false));
-
-        // Using a DependencyProperty as the backing store for AspectRatioValue.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty AspectRatioValueProperty =
-            DependencyProperty.Register("AspectRatioValue", typeof(int), typeof(SizeInput),
-                new PropertyMetadata(1, AspectRatioValChanged));
-
-        private int loadedAspectRatioSize = -1;
-
-        private int loadedSize = -1;
-
-        public SizeInput()
-        {
-            InitializeComponent();
-        }
+using System;
+using System.Windows;
+using System.Windows.Controls;
 
-        public int Size
-        {
-            get => (int)GetValue(SizeProperty);
-            set => SetValue(SizeProperty, value);
-        }
-
-        public bool PreserveAspectRatio
-        {
-            get => (bool)GetValue(PreserveAspectRatioProperty);
-            set => SetValue(PreserveAspectRatioProperty, value);
-        }
+namespace PixiEditor.Views
+{
+    /// <summary>
+    ///     Interaction logic for SizeInput.xaml.
+    /// </summary>
+    public partial class SizeInput : UserControl
+    {
+        // Using a DependencyProperty as the backing store for Size.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty SizeProperty =
+            DependencyProperty.Register("Size", typeof(int), typeof(SizeInput), new PropertyMetadata(1));
 
-        public int AspectRatioValue
-        {
-            get => (int)GetValue(AspectRatioValueProperty);
-            set => SetValue(AspectRatioValueProperty, value);
-        }
-
-        private static void AspectRatioValChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            SizeInput input = (SizeInput)d;
-
-            if (input.PreserveAspectRatio && input.loadedSize != -1)
-            {
-                int newVal = (int)e.NewValue;
-                float ratio = newVal / Math.Clamp(input.loadedAspectRatioSize, 1f, float.MaxValue);
-                input.Size = (int)(input.loadedSize * ratio);
-            }
-        }
-
-        private void uc_LayoutUpdated(object sender, EventArgs e)
-        {
-            if (loadedSize == -1)
-            {
-                loadedSize = Size;
-                loadedAspectRatioSize = AspectRatioValue;
-            }
-        }
-    }
+        // Using a DependencyProperty as the backing store for PreserveAspectRatio.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty PreserveAspectRatioProperty =
+            DependencyProperty.Register("PreserveAspectRatio", typeof(bool), typeof(SizeInput), new PropertyMetadata(false));
+
+        // Using a DependencyProperty as the backing store for AspectRatioValue.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty AspectRatioValueProperty =
+            DependencyProperty.Register("AspectRatioValue", typeof(int), typeof(SizeInput), new PropertyMetadata(1, AspectRatioValChanged));
+
+        private int loadedAspectRatioSize = -1;
+
+        private int loadedSize = -1;
+
+        public SizeInput()
+        {
+            InitializeComponent();
+        }
+
+        public int Size
+        {
+            get => (int)GetValue(SizeProperty);
+            set => SetValue(SizeProperty, value);
+        }
+
+        public bool PreserveAspectRatio
+        {
+            get => (bool)GetValue(PreserveAspectRatioProperty);
+            set => SetValue(PreserveAspectRatioProperty, value);
+        }
+
+        public int AspectRatioValue
+        {
+            get => (int)GetValue(AspectRatioValueProperty);
+            set => SetValue(AspectRatioValueProperty, value);
+        }
+
+        private static void AspectRatioValChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            SizeInput input = (SizeInput)d;
+
+            if (input.PreserveAspectRatio && input.loadedSize != -1)
+            {
+                int newVal = (int)e.NewValue;
+                float ratio = newVal / Math.Clamp(input.loadedAspectRatioSize, 1f, float.MaxValue);
+                input.Size = (int)(input.loadedSize * ratio);
+            }
+        }
+
+        private void Uc_LayoutUpdated(object sender, EventArgs e)
+        {
+            if (loadedSize == -1)
+            {
+                loadedSize = Size;
+                loadedAspectRatioSize = AspectRatioValue;
+            }
+        }
+    }
 }

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

@@ -4,7 +4,7 @@ using System.Windows.Controls;
 namespace PixiEditor.Views
 {
     /// <summary>
-    ///     Interaction logic for SizePicker.xaml
+    ///     Interaction logic for SizePicker.xaml.
     /// </summary>
     public partial class SizePicker : UserControl
     {

+ 0 - 11
PixiEditorTests/ModelsTests/ControllersTests/BitmapManagerTests.cs

@@ -89,15 +89,4 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             Assert.Equal(Colors.Green, bitmapManager.ActiveLayer.GetPixelWithOffset(1, 1));
         }
     }
-
-    public class MockedSinglePixelPen : BitmapOperationTool
-    {
-        public override ToolType ToolType { get; } = ToolType.Pen;
-
-        public override LayerChange[] Use(Layer layer, Coordinates[] mouseMove, Color color)
-        {
-            return Only(
-                BitmapPixelChanges.FromSingleColoredArray(new[] { mouseMove[0] }, color), 0);
-        }
-    }
 }

+ 8 - 4
PixiEditorTests/ModelsTests/ControllersTests/ClipboardControllerTests.cs

@@ -10,14 +10,15 @@ using Xunit;
 namespace PixiEditorTests.ModelsTests.ControllersTests
 {
     public class ClipboardControllerTests
-    {
+    {
+        private const string Text = "Text data";
         private readonly Color testColor = Colors.Coral;
 
         [StaFact]
         public void TestThatClipboardControllerIgnoresNonImageDataInClipboard()
         {
             Clipboard.Clear();
-            Clipboard.SetText("Text data");
+            Clipboard.SetText(Text);
             WriteableBitmap img = ClipboardController.GetImageFromClipboard();
             Assert.Null(img);
         }
@@ -48,8 +49,11 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             testLayer.SetPixel(new Coordinates(4, 4), testColor);
             testLayer2.SetPixel(new Coordinates(5, 5), testColor);
 
-            ClipboardController.CopyToClipboard(new[] { testLayer, testLayer2 },
-                new[] { new Coordinates(4, 4), new Coordinates(5, 5) }, 10, 10);
+            ClipboardController.CopyToClipboard(
+                new[] { testLayer, testLayer2 },
+                new[] { new Coordinates(4, 4), new Coordinates(5, 5) },
+                10,
+                10);
 
             BitmapSource img = Clipboard.GetImage(); // Using default Clipboard get image to avoid false positives from faulty ClipboardController GetImage
 

+ 21 - 0
PixiEditorTests/ModelsTests/ControllersTests/MockedSinglePixelPen.cs

@@ -0,0 +1,21 @@
+using System.Windows.Media;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using Xunit;
+
+namespace PixiEditorTests.ModelsTests.ControllersTests
+{
+    public class MockedSinglePixelPen : BitmapOperationTool
+    {
+        public override ToolType ToolType { get; } = ToolType.Pen;
+
+        public override LayerChange[] Use(Layer layer, Coordinates[] mouseMove, Color color)
+        {
+            return Only(
+                BitmapPixelChanges.FromSingleColoredArray(new[] { mouseMove[0] }, color), 0);
+        }
+    }
+}

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

@@ -37,7 +37,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         {
             bool eventRaised = false;
             Coordinates position = new Coordinates(5, 5);
-            MouseMovementEventArgs args = new MouseMovementEventArgs(new Coordinates());
+            MouseMovementEventArgs args = new MouseMovementEventArgs(default(Coordinates));
 
             MouseMovementController controller = new MouseMovementController();
             controller.MousePositionChanged += (s, e) =>

+ 6 - 3
PixiEditorTests/ModelsTests/ControllersTests/PixelChangesControllerTests.cs

@@ -24,7 +24,8 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             PixelChangesController controller = CreateBasicController();
             Coordinates[] cords = { new Coordinates(5, 3), new Coordinates(7, 2) };
 
-            controller.AddChanges(new LayerChange(
+            controller.AddChanges(
+                new LayerChange(
                     BitmapPixelChanges.FromSingleColoredArray(cords, Colors.Black), 1),
                 new LayerChange(BitmapPixelChanges.FromSingleColoredArray(cords, Colors.Transparent), 1));
 
@@ -38,7 +39,8 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             Coordinates[] cords2 = { new Coordinates(2, 2), new Coordinates(5, 5) };
             PixelChangesController controller = CreateBasicController();
 
-            controller.AddChanges(new LayerChange(
+            controller.AddChanges(
+                new LayerChange(
                     BitmapPixelChanges.FromSingleColoredArray(cords2, Colors.Black), 0),
                 new LayerChange(BitmapPixelChanges.FromSingleColoredArray(cords2, Colors.Transparent), 0));
 
@@ -53,7 +55,8 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             Coordinates[] cords = { new Coordinates(0, 0), new Coordinates(1, 1) };
             PixelChangesController controller = new PixelChangesController();
 
-            controller.AddChanges(new LayerChange(
+            controller.AddChanges(
+                new LayerChange(
                     BitmapPixelChanges.FromSingleColoredArray(cords, Colors.Black), 0),
                 new LayerChange(BitmapPixelChanges.FromSingleColoredArray(cords, Colors.Transparent), 0));
             return controller;

+ 1 - 20
PixiEditorTests/ModelsTests/ControllersTests/ReadonlyUtilityTests.cs

@@ -1,7 +1,5 @@
-using System;
-using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
 using Xunit;
 
 namespace PixiEditorTests.ModelsTests.ControllersTests
@@ -18,21 +16,4 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             Assert.True(toolUsed);
         }
     }
-
-    public class TestReadonlyTool : ReadonlyTool
-    {
-        public TestReadonlyTool(Action toolAction)
-        {
-            ToolAction = toolAction;
-        }
-
-        public Action ToolAction { get; set; }
-
-        public override ToolType ToolType => ToolType.Select;
-
-        public override void Use(Coordinates[] pixels)
-        {
-            ToolAction();
-        }
-    }
 }

+ 80 - 81
PixiEditorTests/ModelsTests/ControllersTests/ShortcutControllerTests.cs

@@ -1,83 +1,82 @@
-using System.Windows.Input;
-using PixiEditor.Helpers;
-using PixiEditor.Models.Controllers.Shortcuts;
-using Xunit;
-
-namespace PixiEditorTests.ModelsTests.ControllersTests
-{
-    public class ShortcutControllerTests
-    {
-        private static ShortcutController GenerateStandardShortcutController(Key shortcutKey, ModifierKeys modifiers, RelayCommand shortcutCommand)
-        {
-            ShortcutController controller = new ShortcutController();
-            controller.Shortcuts.Add(new Shortcut(shortcutKey, shortcutCommand, 0, modifiers));
-            ShortcutController.BlockShortcutExecution = false;
-            return controller;
-        }
-
-        [StaTheory]
-        [InlineData(Key.A, ModifierKeys.None, Key.A, ModifierKeys.None)]
-        [InlineData(Key.A, ModifierKeys.Alt, Key.A, ModifierKeys.Alt)]
-        [InlineData(Key.B, ModifierKeys.Alt | ModifierKeys.Control, Key.B, ModifierKeys.Alt | ModifierKeys.Control)]
-        public void TestThatShortcutControllerExecutesShortcut(Key shortcutKey, ModifierKeys shortcutModifiers, Key clickedKey, ModifierKeys clickedModifiers)
-        {
-            int result = -1;
-            RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
-            ShortcutController controller = GenerateStandardShortcutController(shortcutKey, shortcutModifiers, shortcutCommand);
-
-            controller.KeyPressed(clickedKey, clickedModifiers);
-            Assert.Equal(0, result);
-        }
-
-        [StaTheory]
-        [InlineData(Key.B, ModifierKeys.None, Key.A, ModifierKeys.None)]
-        [InlineData(Key.A, ModifierKeys.Alt, Key.A, ModifierKeys.None)]
-        [InlineData(Key.C, ModifierKeys.Alt | ModifierKeys.Control, Key.C, ModifierKeys.Alt | ModifierKeys.Windows)]
-        public void TestThatShortcutControllerNotExecutesShortcut(Key shortcutKey, ModifierKeys shortcutModifiers, Key clickedKey, ModifierKeys clickedModifiers)
-        {
-            int result = -1;
-            RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
-            ShortcutController controller = GenerateStandardShortcutController(shortcutKey, shortcutModifiers, shortcutCommand);
+using System.Windows.Input;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers.Shortcuts;
+using Xunit;
 
-            controller.KeyPressed(clickedKey, clickedModifiers);
-            Assert.Equal(-1, result);
-        }
-
-        [StaFact]
-        public void TestThatShortcutControllerIsBlocked()
-        {
-            int result = -1;
-            RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
-
-            ShortcutController controller = GenerateStandardShortcutController(Key.A, ModifierKeys.None, shortcutCommand);
-            ShortcutController.BlockShortcutExecution = true;
-
-            controller.KeyPressed(Key.A, ModifierKeys.None);
-            Assert.Equal(-1, result);
-        }
-
-        [StaFact]
-        public void TestThatShortcutControllerPicksCorrectShortcut()
-        {
-            int result = -1;
-            RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
-
-            ShortcutController controller = GenerateStandardShortcutController(Key.A, ModifierKeys.None, shortcutCommand);
-            controller.Shortcuts.Add(new Shortcut(Key.A, shortcutCommand, 1, ModifierKeys.Control));
-
-            controller.KeyPressed(Key.A, ModifierKeys.Control);
-            Assert.Equal(1, result);
-        }
-
-        [StaFact]
-        public void TestThatKeyPressedSetsLastShortcut()
-        {
-            ShortcutController controller = GenerateStandardShortcutController(Key.A, ModifierKeys.None,
-                new RelayCommand(parameter => { }));
-
-            Assert.Null(controller.LastShortcut);
-            controller.KeyPressed(Key.A, ModifierKeys.None);
-            Assert.Equal(controller.Shortcuts[0], controller.LastShortcut);
-        }
-    }
+namespace PixiEditorTests.ModelsTests.ControllersTests
+{
+    public class ShortcutControllerTests
+    {
+        [StaTheory]
+        [InlineData(Key.A, ModifierKeys.None, Key.A, ModifierKeys.None)]
+        [InlineData(Key.A, ModifierKeys.Alt, Key.A, ModifierKeys.Alt)]
+        [InlineData(Key.B, ModifierKeys.Alt | ModifierKeys.Control, Key.B, ModifierKeys.Alt | ModifierKeys.Control)]
+        public void TestThatShortcutControllerExecutesShortcut(Key shortcutKey, ModifierKeys shortcutModifiers, Key clickedKey, ModifierKeys clickedModifiers)
+        {
+            int result = -1;
+            RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
+            ShortcutController controller = GenerateStandardShortcutController(shortcutKey, shortcutModifiers, shortcutCommand);
+
+            controller.KeyPressed(clickedKey, clickedModifiers);
+            Assert.Equal(0, result);
+        }
+
+        [StaTheory]
+        [InlineData(Key.B, ModifierKeys.None, Key.A, ModifierKeys.None)]
+        [InlineData(Key.A, ModifierKeys.Alt, Key.A, ModifierKeys.None)]
+        [InlineData(Key.C, ModifierKeys.Alt | ModifierKeys.Control, Key.C, ModifierKeys.Alt | ModifierKeys.Windows)]
+        public void TestThatShortcutControllerNotExecutesShortcut(Key shortcutKey, ModifierKeys shortcutModifiers, Key clickedKey, ModifierKeys clickedModifiers)
+        {
+            int result = -1;
+            RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
+            ShortcutController controller = GenerateStandardShortcutController(shortcutKey, shortcutModifiers, shortcutCommand);
+
+            controller.KeyPressed(clickedKey, clickedModifiers);
+            Assert.Equal(-1, result);
+        }
+
+        [StaFact]
+        public void TestThatShortcutControllerIsBlocked()
+        {
+            int result = -1;
+            RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
+
+            ShortcutController controller = GenerateStandardShortcutController(Key.A, ModifierKeys.None, shortcutCommand);
+            ShortcutController.BlockShortcutExecution = true;
+
+            controller.KeyPressed(Key.A, ModifierKeys.None);
+            Assert.Equal(-1, result);
+        }
+
+        [StaFact]
+        public void TestThatShortcutControllerPicksCorrectShortcut()
+        {
+            int result = -1;
+            RelayCommand shortcutCommand = new RelayCommand(arg => { result = (int)arg; });
+
+            ShortcutController controller = GenerateStandardShortcutController(Key.A, ModifierKeys.None, shortcutCommand);
+            controller.Shortcuts.Add(new Shortcut(Key.A, shortcutCommand, 1, ModifierKeys.Control));
+
+            controller.KeyPressed(Key.A, ModifierKeys.Control);
+            Assert.Equal(1, result);
+        }
+
+        [StaFact]
+        public void TestThatKeyPressedSetsLastShortcut()
+        {
+            ShortcutController controller = GenerateStandardShortcutController(Key.A, ModifierKeys.None, new RelayCommand(parameter => { }));
+
+            Assert.Null(controller.LastShortcut);
+            controller.KeyPressed(Key.A, ModifierKeys.None);
+            Assert.Equal(controller.Shortcuts[0], controller.LastShortcut);
+        }
+
+        private static ShortcutController GenerateStandardShortcutController(Key shortcutKey, ModifierKeys modifiers, RelayCommand shortcutCommand)
+        {
+            ShortcutController controller = new ShortcutController();
+            controller.Shortcuts.Add(new Shortcut(shortcutKey, shortcutCommand, 0, modifiers));
+            ShortcutController.BlockShortcutExecution = false;
+            return controller;
+        }
+    }
 }

+ 7 - 0
PixiEditorTests/ModelsTests/ControllersTests/TestPropertyClass.cs

@@ -0,0 +1,7 @@
+namespace PixiEditorTests.ModelsTests.ControllersTests
+{
+    public class TestPropertyClass
+    {
+        public int IntProperty { get; set; }
+    }
+}

+ 23 - 0
PixiEditorTests/ModelsTests/ControllersTests/TestReadonlyTool.cs

@@ -0,0 +1,23 @@
+using System;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+
+namespace PixiEditorTests.ModelsTests.ControllersTests
+{
+    public class TestReadonlyTool : ReadonlyTool
+    {
+        public TestReadonlyTool(Action toolAction)
+        {
+            ToolAction = toolAction;
+        }
+
+        public Action ToolAction { get; set; }
+
+        public override ToolType ToolType => ToolType.Select;
+
+        public override void Use(Coordinates[] pixels)
+        {
+            ToolAction();
+        }
+    }
+}

+ 7 - 9
PixiEditorTests/ModelsTests/ControllersTests/UndoManagerTests.cs

@@ -100,7 +100,8 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             int newVal = 5;
 
             UndoManager.AddUndoChange(
-                new Change("ExampleProperty",
+                new Change(
+                    "ExampleProperty",
                     ReverseProcess,
                     new object[] { ExampleProperty },
                     newVal));
@@ -123,7 +124,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         {
             PrepareUndoManagerForTest();
             int newVal = 5;
-            UndoManager.AddUndoChange(new Change(ReverseProcess, new object[] { ExampleProperty }, ReverseProcess,
+            UndoManager.AddUndoChange(new Change(
+                ReverseProcess,
+                new object[] { ExampleProperty },
+                ReverseProcess,
                 new object[] { newVal }));
 
             ExampleProperty = newVal;
@@ -145,8 +149,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             PrepareUndoManagerForTest();
             int newVal = 5;
 
-            UndoManager.AddUndoChange(new Change("TestPropClass.IntProperty", TestPropClass.IntProperty,
-                newVal));
+            UndoManager.AddUndoChange(new Change("TestPropClass.IntProperty", TestPropClass.IntProperty, newVal));
 
             TestPropClass.IntProperty = newVal;
 
@@ -175,9 +178,4 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             TestPropClass = new TestPropertyClass { IntProperty = 0 };
         }
     }
-
-    public class TestPropertyClass
-    {
-        public int IntProperty { get; set; }
-    }
 }

+ 2 - 2
PixiEditorTests/ModelsTests/DataHoldersTests/BitmapPixelChangesTests.cs

@@ -37,8 +37,8 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [Fact]
         public void TestThatFromArraysThrowsError()
         {
-            Assert.Throws<ArrayLengthMismatchException>
-                (() => BitmapPixelChanges.FromArrays(new[] { new Coordinates(0, 0) }, new[] { Colors.Red, Colors.Green }));
+            Assert.Throws<ArrayLengthMismatchException>(
+                () => BitmapPixelChanges.FromArrays(new[] { new Coordinates(0, 0) }, new[] { Colors.Red, Colors.Green }));
         }
 
         [Fact]

+ 6 - 2
PixiEditorTests/ModelsTests/DataHoldersTests/DocumentTests.cs

@@ -47,7 +47,9 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
                 ActiveDocument = document
             };
             manager.AddNewLayer("test");
-            manager.ActiveLayer.SetPixel(new Coordinates((int)Math.Ceiling(initialWidth / 2f),
+            manager.ActiveLayer.SetPixel(
+                new Coordinates(
+                (int)Math.Ceiling(initialWidth / 2f),
                 (int)Math.Ceiling(initialHeight / 2f)), Colors.Black);
 
             manager.ActiveLayer.SetPixel(new Coordinates(additionalPixelX, additionalPixelY), Colors.Black);
@@ -71,7 +73,9 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
                 ActiveDocument = document
             };
             manager.AddNewLayer("test");
-            manager.ActiveLayer.SetPixel(new Coordinates((int)Math.Ceiling(initialWidth / 2f),
+            manager.ActiveLayer.SetPixel(
+                new Coordinates(
+                (int)Math.Ceiling(initialWidth / 2f),
                 (int)Math.Ceiling(initialHeight / 2f)), Colors.Black); // Set pixel in center
 
             manager.AddNewLayer("test2");

+ 2 - 1
PixiEditorTests/ModelsTests/DataHoldersTests/SerializableDocumentTests.cs

@@ -47,7 +47,8 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             Assert.Equal(document.Height, convertedDocument.Height);
             Assert.Equal(document.Width, convertedDocument.Width);
             Assert.Equal(document.Swatches, convertedDocument.Swatches);
-            Assert.Equal(document.Layers.Select(x => x.LayerBitmap.ToByteArray()),
+            Assert.Equal(
+                document.Layers.Select(x => x.LayerBitmap.ToByteArray()),
                 convertedDocument.Layers.Select(x => x.LayerBitmap.ToByteArray()));
         }
 

+ 0 - 1
PixiEditorTests/ModelsTests/IO/ImporterTests.cs

@@ -11,7 +11,6 @@ namespace PixiEditorTests.ModelsTests.IO
         private readonly string testImagePath;
 
         // I am not testing ImportDocument, because it's just a wrapper for BinarySerialization which is tested.
-
         public ImporterTests()
         {
             testImagePath = $"{Environment.CurrentDirectory}\\..\\..\\..\\ModelsTests\\IO\\TestImage.png";

+ 1 - 0
PixiEditorTests/ModelsTests/ImageManipulationTests/BitmapUtilsTests.cs

@@ -93,6 +93,7 @@ namespace PixiEditorTests.ModelsTests.ImageManipulationTests
             Assert.Single(colors.Where(x => x == Colors.Green));
             Assert.Single(colors.Where(x => x == Colors.Red));
             Assert.Equal(6, colors.Count(x => x.A == 0)); // 6 because layer is 4 pixels,
+
             // 2 * 4 = 8, 2 other color pixels, so 8 - 2 = 6
         }
     }

+ 130 - 129
PixiEditorTests/ModelsTests/LayersTests/LayerTests.cs

@@ -1,140 +1,141 @@
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using Xunit;
-
-namespace PixiEditorTests.ModelsTests.LayersTests
-{
-    public class LayerTests
-    {
-        [Fact]
-        public void TestThatEmptyLayerGeneratesCorrectly()
-        {
-            Layer layer = new Layer("layer");
-
-            Assert.Equal("layer", layer.Name);
-            Assert.Equal(0, layer.Width);
-            Assert.Equal(0, layer.Height);
-            Assert.Equal(1, layer.LayerBitmap.PixelWidth);
-            Assert.Equal(1, layer.LayerBitmap.PixelHeight);
-        }
-
-        [Fact]
-        public void TestThatEmptyLayerWithSizeGeneratesCorrectly()
-        {
-            Layer layer = new Layer("layer", 10, 10);
-
-            Assert.Equal("layer", layer.Name);
-            Assert.Equal(10, layer.Width);
-            Assert.Equal(10, layer.Height);
-            Assert.Equal(10, layer.LayerBitmap.PixelWidth);
-            Assert.Equal(10, layer.LayerBitmap.PixelHeight);
-        }
-
-        [Fact]
-        public void TestThatLayerFromBitmapGeneratesCorrectly()
-        {
-            WriteableBitmap bmp = BitmapFactory.New(10, 10);
-
-            Layer layer = new Layer("layer", bmp);
-
-            Assert.Equal("layer", layer.Name);
-            Assert.Equal(10, layer.Width);
-            Assert.Equal(10, layer.Height);
-            Assert.Equal(10, layer.LayerBitmap.PixelWidth);
-            Assert.Equal(10, layer.LayerBitmap.PixelHeight);
-        }
-
-        [Fact]
-        public void TestThatCloneClonesCorrectly()
-        {
-            Layer layer = new Layer("test", 5, 2);
-
-            Layer clone = layer.Clone();
-
-            Assert.Equal(layer.Name, clone.Name);
-            Assert.Equal(layer.Offset, clone.Offset);
-            Assert.Equal(layer.Width, clone.Width);
-            Assert.Equal(layer.Height, clone.Height);
-            Assert.Equal(layer.MaxHeight, clone.MaxHeight);
-            Assert.Equal(layer.MaxWidth, clone.MaxWidth);
-            Assert.Equal(layer.Opacity, clone.Opacity);
-            Assert.Equal(layer.IsVisible, clone.IsVisible);
-            Assert.Equal(layer.IsRenaming, clone.IsRenaming);
-            Assert.Equal(layer.ConvertBitmapToBytes(), clone.ConvertBitmapToBytes());
-        }
-
-        [Fact]
-        public void TestThatCloneIsMakingDeepCopyOfBitmap()
-        {
-            Layer layer = new Layer("test", 5, 2);
-
-            Layer clone = layer.Clone();
-
-            clone.LayerBitmap.SetPixel(0, 0, Colors.Green); // Actually we are checking if modifying clone bitmap does not affect original
-
-            Assert.NotEqual(Colors.Green, layer.GetPixel(0, 0));
-        }
-
-        [Fact]
-        public void TestThatResizeResizesBitmap()
-        {
-            Layer layer = new Layer("layer", 1, 1);
-
-            layer.SetPixel(new Coordinates(0, 0), Colors.Black);
-
-            layer.Resize(2, 2, 2, 2);
-
-            Assert.Equal(2, layer.Width);
-            Assert.Equal(2, layer.Height);
-            Assert.Equal(2, layer.MaxWidth);
-            Assert.Equal(2, layer.MaxHeight);
-
-            for (int y = 0; y < layer.Height; y++) // 4 is new area of bitmap
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using Xunit;
+
+namespace PixiEditorTests.ModelsTests.LayersTests
+{
+    public class LayerTests
+    {
+        [Fact]
+        public void TestThatEmptyLayerGeneratesCorrectly()
+        {
+            Layer layer = new Layer("layer");
+
+            Assert.Equal("layer", layer.Name);
+            Assert.Equal(0, layer.Width);
+            Assert.Equal(0, layer.Height);
+            Assert.Equal(1, layer.LayerBitmap.PixelWidth);
+            Assert.Equal(1, layer.LayerBitmap.PixelHeight);
+        }
+
+        [Fact]
+        public void TestThatEmptyLayerWithSizeGeneratesCorrectly()
+        {
+            Layer layer = new Layer("layer", 10, 10);
+
+            Assert.Equal("layer", layer.Name);
+            Assert.Equal(10, layer.Width);
+            Assert.Equal(10, layer.Height);
+            Assert.Equal(10, layer.LayerBitmap.PixelWidth);
+            Assert.Equal(10, layer.LayerBitmap.PixelHeight);
+        }
+
+        [Fact]
+        public void TestThatLayerFromBitmapGeneratesCorrectly()
+        {
+            WriteableBitmap bmp = BitmapFactory.New(10, 10);
+
+            Layer layer = new Layer("layer", bmp);
+
+            Assert.Equal("layer", layer.Name);
+            Assert.Equal(10, layer.Width);
+            Assert.Equal(10, layer.Height);
+            Assert.Equal(10, layer.LayerBitmap.PixelWidth);
+            Assert.Equal(10, layer.LayerBitmap.PixelHeight);
+        }
+
+        [Fact]
+        public void TestThatCloneClonesCorrectly()
+        {
+            Layer layer = new Layer("test", 5, 2);
+
+            Layer clone = layer.Clone();
+
+            Assert.Equal(layer.Name, clone.Name);
+            Assert.Equal(layer.Offset, clone.Offset);
+            Assert.Equal(layer.Width, clone.Width);
+            Assert.Equal(layer.Height, clone.Height);
+            Assert.Equal(layer.MaxHeight, clone.MaxHeight);
+            Assert.Equal(layer.MaxWidth, clone.MaxWidth);
+            Assert.Equal(layer.Opacity, clone.Opacity);
+            Assert.Equal(layer.IsVisible, clone.IsVisible);
+            Assert.Equal(layer.IsRenaming, clone.IsRenaming);
+            Assert.Equal(layer.ConvertBitmapToBytes(), clone.ConvertBitmapToBytes());
+        }
+
+        [Fact]
+        public void TestThatCloneIsMakingDeepCopyOfBitmap()
+        {
+            Layer layer = new Layer("test", 5, 2);
+
+            Layer clone = layer.Clone();
+
+            clone.LayerBitmap.SetPixel(0, 0, Colors.Green); // Actually we are checking if modifying clone bitmap does not affect original
+
+            Assert.NotEqual(Colors.Green, layer.GetPixel(0, 0));
+        }
+
+        [Fact]
+        public void TestThatResizeResizesBitmap()
+        {
+            Layer layer = new Layer("layer", 1, 1);
+
+            layer.SetPixel(new Coordinates(0, 0), Colors.Black);
+
+            layer.Resize(2, 2, 2, 2);
+
+            Assert.Equal(2, layer.Width);
+            Assert.Equal(2, layer.Height);
+            Assert.Equal(2, layer.MaxWidth);
+            Assert.Equal(2, layer.MaxHeight);
+
+            // 4 is new area of bitmap
+            for (int y = 0; y < layer.Height; y++)
             {
                 for (int x = 0; x < layer.Width; x++)
                 {
                     Assert.Equal(Colors.Black, layer.GetPixel(x, y));
                 }
             }
-        }
-
-        [Fact]
-        public void TestThatGetPixelReturnsTransparentIfOutOfBounds()
-        {
-            Layer layer = new Layer("layer");
-
-            Assert.Equal(0, layer.GetPixel(-1, 999).A);
-        }
-
-        [Fact]
-        public void TestThatSetPixelsSetsPixels() // This also tests if Dynamic Resize works
-        {
-            Coordinates[] pixels = { new Coordinates(4, 2), new Coordinates(0, 0), new Coordinates(15, 2) };
-
-            Layer layer = new Layer("layer");
-
-            layer.SetPixels(BitmapPixelChanges.FromSingleColoredArray(pixels, Colors.Green));
-
+        }
+
+        [Fact]
+        public void TestThatGetPixelReturnsTransparentIfOutOfBounds()
+        {
+            Layer layer = new Layer("layer");
+
+            Assert.Equal(0, layer.GetPixel(-1, 999).A);
+        }
+
+        [Fact]
+        public void TestThatSetPixelsSetsPixels() // This also tests if Dynamic Resize works
+        {
+            Coordinates[] pixels = { new Coordinates(4, 2), new Coordinates(0, 0), new Coordinates(15, 2) };
+
+            Layer layer = new Layer("layer");
+
+            layer.SetPixels(BitmapPixelChanges.FromSingleColoredArray(pixels, Colors.Green));
+
             for (int i = 0; i < pixels.Length; i++)
             {
                 Assert.Equal(Colors.Green, layer.GetPixelWithOffset(pixels[i].X, pixels[i].Y));
             }
-        }
-
-        [Fact]
-        public void TestThatClipCanvasResizesBitmapCorrectly()
-        {
-            Layer layer = new Layer("layer", 10, 10);
-            layer.SetPixel(new Coordinates(4, 4), Colors.Blue);
-
-            layer.ClipCanvas();
-
-            Assert.Equal(1, layer.Width);
-            Assert.Equal(1, layer.Height);
-            Assert.Equal(Colors.Blue, layer.GetPixel(0, 0));
-        }
-    }
+        }
+
+        [Fact]
+        public void TestThatClipCanvasResizesBitmapCorrectly()
+        {
+            Layer layer = new Layer("layer", 10, 10);
+            layer.SetPixel(new Coordinates(4, 4), Colors.Blue);
+
+            layer.ClipCanvas();
+
+            Assert.Equal(1, layer.Width);
+            Assert.Equal(1, layer.Height);
+            Assert.Equal(Colors.Blue, layer.GetPixel(0, 0));
+        }
+    }
 }

+ 1 - 0
PixiEditorTests/ModelsTests/ToolsTests/BrightnessToolTests.cs

@@ -12,6 +12,7 @@ namespace PixiEditorTests.ModelsTests.ToolsTests
         [StaTheory]
         [InlineData(5, 12, 12, 12)]
         [InlineData(-5, 242, 242, 242)]
+
         // If correction factor is negative, testing color will be white, otherwise black
         public void TestThatBrightnessToolChangesPixelBrightness(float correctionFactor, byte expectedR, byte expectedG, byte expectedB)
         {

+ 29 - 27
PixiEditorTests/ModelsTests/ToolsTests/RectangleToolTests.cs

@@ -1,28 +1,30 @@
-using System.Linq;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.Tools;
-using Xunit;
-
-namespace PixiEditorTests.ModelsTests.ToolsTests
-{
-    [Collection("Application collection")]
-    public class RectangleToolTests
-    {
-        [StaTheory]
-        [InlineData(0, 0, 2, 2)]
-        [InlineData(0, 0, 9, 9)]
-        [InlineData(5, 5, 6, 6)]
-        [InlineData(0, 0, 15, 15)]
-        public void TestThatCreateRectangleCalculatesCorrectOutlineWithOneThickness(int startX, int startY, int endX, int endY)
-        {
-            RectangleTool tool = new RectangleTool();
-
-            System.Collections.Generic.IEnumerable<Coordinates> outline = tool.CreateRectangle(new Coordinates(startX, startY),
-                new Coordinates(endX, endY), 1);
-
-            int expectedBorderPoints = (endX - startX) * 2 + (endY - startX) * 2;
-
-            Assert.Equal(expectedBorderPoints, outline.Count());
-        }
-    }
+using System.Linq;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.Tools;
+using Xunit;
+
+namespace PixiEditorTests.ModelsTests.ToolsTests
+{
+    [Collection("Application collection")]
+    public class RectangleToolTests
+    {
+        [StaTheory]
+        [InlineData(0, 0, 2, 2)]
+        [InlineData(0, 0, 9, 9)]
+        [InlineData(5, 5, 6, 6)]
+        [InlineData(0, 0, 15, 15)]
+        public void TestThatCreateRectangleCalculatesCorrectOutlineWithOneThickness(int startX, int startY, int endX, int endY)
+        {
+            RectangleTool tool = new RectangleTool();
+
+            System.Collections.Generic.IEnumerable<Coordinates> outline = tool.CreateRectangle(
+                new Coordinates(startX, startY),
+                new Coordinates(endX, endY),
+                1);
+
+            int expectedBorderPoints = ((endX - startX) * 2) + ((endY - startX) * 2);
+
+            Assert.Equal(expectedBorderPoints, outline.Count());
+        }
+    }
 }

+ 2 - 1
PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

@@ -155,7 +155,8 @@ namespace PixiEditorTests.ViewModelsTests
 
             viewModel.SelectAllCommand.Execute(null);
 
-            Assert.Equal(viewModel.BitmapManager.ActiveDocument.Width * viewModel.BitmapManager.ActiveDocument.Height,
+            Assert.Equal(
+                viewModel.BitmapManager.ActiveDocument.Width * viewModel.BitmapManager.ActiveDocument.Height,
                 viewModel.ActiveSelection.SelectedPoints.Count);
         }