Browse Source

Merge pull request #83 from PixiEditor/master

Shared toolbar settings hotfix and new Move Viewport Tool
Krzysztof Krysiński 4 years ago
parent
commit
254d9b418c
100 changed files with 3655 additions and 2980 deletions
  1. 13 0
      Custom.ruleset
  2. 11 0
      Directory.Build.props
  3. 2 3
      PixiEditor.UpdateInstaller/App.xaml
  4. 3 9
      PixiEditor.UpdateInstaller/App.xaml.cs
  5. 11 7
      PixiEditor.UpdateInstaller/AssemblyInfo.cs
  6. 22 19
      PixiEditor.UpdateInstaller/Extensions.cs
  7. 7 8
      PixiEditor.UpdateInstaller/MainWindow.xaml
  8. 5 6
      PixiEditor.UpdateInstaller/MainWindow.xaml.cs
  9. 5 5
      PixiEditor.UpdateInstaller/ViewModelBase.cs
  10. 22 28
      PixiEditor.UpdateInstaller/ViewModelMain.cs
  11. 4 5
      PixiEditor.UpdateModule/Asset.cs
  12. 15 11
      PixiEditor.UpdateModule/ReleaseInfo.cs
  13. 34 29
      PixiEditor.UpdateModule/UpdateChecker.cs
  14. 3 2
      PixiEditor.UpdateModule/UpdateDownloader.cs
  15. 20 14
      PixiEditor.UpdateModule/UpdateInstaller.cs
  16. 2 2
      PixiEditor.UpdateModule/UpdateProgressChangedEventArgs.cs
  17. 50 0
      PixiEditor.sln
  18. 4 2
      PixiEditor/Exceptions/ArrayLengthMismatchException.cs
  19. 105 93
      PixiEditor/Helpers/Behaviours/AllowableCharactersTextBoxBehavior.cs
  20. 71 63
      PixiEditor/Helpers/Behaviours/HintTextBehavior.cs
  21. 62 57
      PixiEditor/Helpers/Behaviours/MouseBehaviour.cs
  22. 116 99
      PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs
  23. 5 0
      PixiEditor/Helpers/Converters/BoolToColorConverter.cs
  24. 5 0
      PixiEditor/Helpers/Converters/BoolToIntConverter.cs
  25. 3 5
      PixiEditor/Helpers/Converters/DoubleToIntConverter.cs
  26. 9 7
      PixiEditor/Helpers/Converters/FloatNormalizeConverter.cs
  27. 9 2
      PixiEditor/Helpers/Converters/OppositeVisibilityConverter.cs
  28. 33 25
      PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs
  29. 29 20
      PixiEditor/Helpers/Extensions/DictionaryHelper.cs
  30. 55 45
      PixiEditor/Helpers/GlobalMouseHook.cs
  31. 42 52
      PixiEditor/Helpers/RelayCommand.cs
  32. 4 2
      PixiEditor/Helpers/UI/ReversedOrderStackPanel.cs
  33. 1 1
      PixiEditor/Helpers/Validators/SizeValidationRule.cs
  34. BIN
      PixiEditor/Images/MoveViewportImage.png
  35. 154 137
      PixiEditor/Models/Colors/ExColor.cs
  36. 21 0
      PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs
  37. 121 102
      PixiEditor/Models/Controllers/BitmapManager.cs
  38. 60 51
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  39. 106 98
      PixiEditor/Models/Controllers/ClipboardController.cs
  40. 18 0
      PixiEditor/Models/Controllers/LayersChangedEventArgs.cs
  41. 32 16
      PixiEditor/Models/Controllers/MouseMovementController.cs
  42. 15 0
      PixiEditor/Models/Controllers/MouseMovementEventArgs.cs
  43. 51 37
      PixiEditor/Models/Controllers/PixelChangesController.cs
  44. 13 8
      PixiEditor/Models/Controllers/Shortcuts/Shortcut.cs
  45. 13 6
      PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs
  46. 96 84
      PixiEditor/Models/Controllers/UndoManager.cs
  47. 82 70
      PixiEditor/Models/DataHolders/BitmapPixelChanges.cs
  48. 97 77
      PixiEditor/Models/DataHolders/Change.cs
  49. 327 304
      PixiEditor/Models/DataHolders/Document.cs
  50. 21 0
      PixiEditor/Models/DataHolders/DocumentSizeChangedEventArgs.cs
  51. 4 4
      PixiEditor/Models/DataHolders/LayerChange.cs
  52. 13 15
      PixiEditor/Models/DataHolders/Selection.cs
  53. 9 6
      PixiEditor/Models/DataHolders/SerializableDocument.cs
  54. 5 1
      PixiEditor/Models/Dialogs/ConfirmationDialog.cs
  55. 22 23
      PixiEditor/Models/Dialogs/ExportFileDialog.cs
  56. 19 18
      PixiEditor/Models/Dialogs/ImportFileDialog.cs
  57. 11 11
      PixiEditor/Models/Dialogs/NewFileDialog.cs
  58. 19 18
      PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs
  59. 4 7
      PixiEditor/Models/Enums/BrightnessMode.cs
  60. 4 7
      PixiEditor/Models/Enums/CapType.cs
  61. 1 1
      PixiEditor/Models/Enums/FileType.cs
  62. 2 2
      PixiEditor/Models/Events/DocumentChangedEventArgs.cs
  63. 1 1
      PixiEditor/Models/IO/BinarySerialization.cs
  64. 96 95
      PixiEditor/Models/IO/Exporter.cs
  65. 8 8
      PixiEditor/Models/IO/Importer.cs
  66. 29 25
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  67. 98 77
      PixiEditor/Models/ImageManipulation/Morphology.cs
  68. 8 5
      PixiEditor/Models/ImageManipulation/Transform.cs
  69. 8 8
      PixiEditor/Models/Layers/BasicLayer.cs
  70. 492 462
      PixiEditor/Models/Layers/Layer.cs
  71. 76 63
      PixiEditor/Models/Layers/SerializableLayer.cs
  72. 8 8
      PixiEditor/Models/Position/Coordinates.cs
  73. 62 47
      PixiEditor/Models/Position/CoordinatesCalculator.cs
  74. 14 13
      PixiEditor/Models/Position/DoubleCords.cs
  75. 9 12
      PixiEditor/Models/Position/MousePositionConverter.cs
  76. 2 1
      PixiEditor/Models/Processes/ProcessHelper.cs
  77. 2 0
      PixiEditor/Models/Tools/BitmapOperationTool.cs
  78. 38 20
      PixiEditor/Models/Tools/ShapeTool.cs
  79. 30 10
      PixiEditor/Models/Tools/Tool.cs
  80. 4 2
      PixiEditor/Models/Tools/ToolSettings/Settings/BoolSetting.cs
  81. 2 1
      PixiEditor/Models/Tools/ToolSettings/Settings/ColorSetting.cs
  82. 6 6
      PixiEditor/Models/Tools/ToolSettings/Settings/DropdownSetting.cs
  83. 43 37
      PixiEditor/Models/Tools/ToolSettings/Settings/FloatSetting.cs
  84. 44 40
      PixiEditor/Models/Tools/ToolSettings/Settings/Setting.cs
  85. 7 6
      PixiEditor/Models/Tools/ToolSettings/Settings/SizeSetting.cs
  86. 1 1
      PixiEditor/Models/Tools/ToolSettings/Toolbars/BasicToolbar.cs
  87. 1 1
      PixiEditor/Models/Tools/ToolSettings/Toolbars/BrightnessToolToolbar.cs
  88. 1 1
      PixiEditor/Models/Tools/ToolSettings/Toolbars/SelectToolToolbar.cs
  89. 74 66
      PixiEditor/Models/Tools/ToolSettings/Toolbars/Toolbar.cs
  90. 1 0
      PixiEditor/Models/Tools/ToolType.cs
  91. 33 23
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  92. 47 35
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  93. 5 6
      PixiEditor/Models/Tools/Tools/ColorPickerTool.cs
  94. 4 4
      PixiEditor/Models/Tools/Tools/EraserTool.cs
  95. 40 28
      PixiEditor/Models/Tools/Tools/FloodFill.cs
  96. 34 25
      PixiEditor/Models/Tools/Tools/LineTool.cs
  97. 103 82
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  98. 52 0
      PixiEditor/Models/Tools/Tools/MoveViewportTool.cs
  99. 5 4
      PixiEditor/Models/Tools/Tools/PenTool.cs
  100. 45 33
      PixiEditor/Models/Tools/Tools/RectangleTool.cs

+ 13 - 0
Custom.ruleset

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RuleSet Name="Name" Description="Description" ToolsVersion="16.0">
+  <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" />
+    <Rule Id="SA1310" Action="None" />
+  </Rules>
+</RuleSet>

+ 11 - 0
Directory.Build.props

@@ -0,0 +1,11 @@
+<Project>
+    <PropertyGroup>
+        <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
+    </PropertyGroup>
+    <ItemGroup>
+        <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
+    </ItemGroup>
+    <ItemGroup>
+        <AdditionalFiles Include="../stylecop.json" />
+    </ItemGroup>
+</Project>

+ 2 - 3
PixiEditor.UpdateInstaller/App.xaml

@@ -1,9 +1,8 @@
 <Application x:Class="PixiEditor.UpdateInstaller.App"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:local="clr-namespace:PixiEditor.UpdateInstaller"
              StartupUri="MainWindow.xaml">
     <Application.Resources>
-         
+
     </Application.Resources>
-</Application>
+</Application>

+ 3 - 9
PixiEditor.UpdateInstaller/App.xaml.cs

@@ -1,17 +1,11 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
+using System.Windows;
 
 namespace PixiEditor.UpdateInstaller
 {
     /// <summary>
-    /// Interaction logic for App.xaml
+    ///     Interaction logic for App.xaml.
     /// </summary>
     public partial class App : Application
     {
     }
-}
+}

+ 11 - 7
PixiEditor.UpdateInstaller/AssemblyInfo.cs

@@ -1,10 +1,14 @@
 using System.Windows;
 
 [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)
-    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)
-)]
+    ResourceDictionaryLocation.None,
+    ResourceDictionaryLocation.SourceAssembly)
+
+// ResourceDictionaryLocation.None - where theme specific resource dictionaries are located
+// (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)
+]

+ 22 - 19
PixiEditor.UpdateInstaller/Extensions.cs

@@ -1,24 +1,27 @@
 using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
 
 namespace PixiEditor.UpdateInstaller
 {
     public static class Extensions
-	{
-		[System.Runtime.InteropServices.DllImport("kernel32.dll")]
-		static extern uint GetModuleFileName(IntPtr hModule, System.Text.StringBuilder lpFilename, int nSize);
-		static readonly int MAX_PATH = 255;
-		public static string GetExecutablePath()
-		{
-			if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
-			{
-				var sb = new System.Text.StringBuilder(MAX_PATH);
-				GetModuleFileName(IntPtr.Zero, sb, MAX_PATH);
-				return sb.ToString();
-			}
-			else
-			{
-				return System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
-			}
-		}
-	}
-}
+    {
+        private const int MaxPath = 255;
+
+        public static string GetExecutablePath()
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                StringBuilder sb = new StringBuilder(MaxPath);
+                GetModuleFileName(IntPtr.Zero, sb, MaxPath);
+                return sb.ToString();
+            }
+
+            return Process.GetCurrentProcess().MainModule?.FileName;
+        }
+
+        [DllImport("kernel32.dll")]
+        private static extern uint GetModuleFileName(IntPtr hModule, StringBuilder lpFilename, int nSize);
+    }
+}

+ 7 - 8
PixiEditor.UpdateInstaller/MainWindow.xaml

@@ -3,23 +3,22 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        xmlns:local="clr-namespace:PixiEditor.UpdateInstaller"
         mc:Ignorable="d"
         Title="MainWindow" Height="350" Width="250" Background="#2D2D30" ResizeMode="NoResize"
         WindowStyle="None" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded">
     <WindowChrome.WindowChrome>
         <WindowChrome ResizeBorderThickness="6"
-            CaptionHeight="30"/>
+                      CaptionHeight="30" />
     </WindowChrome.WindowChrome>
     <Grid>
         <Grid.RowDefinitions>
-            <RowDefinition Height="130"/>
-            <RowDefinition/>
+            <RowDefinition Height="130" />
+            <RowDefinition />
         </Grid.RowDefinitions>
-        <Image Source="Images/PixiEditorLogo.png" Width="75" Height="75" Grid.Row="0"/>
+        <Image Source="Images/PixiEditorLogo.png" Width="75" Height="75" Grid.Row="0" />
         <StackPanel Grid.Row="1" Margin="0,20,0,0">
-            <Label Content="Installing update" HorizontalAlignment="Center" Foreground="White" FontSize="20"/>
-            <ProgressBar Margin="0,20,0,0" Height="20" Width="200" Value="{Binding ProgressValue}"/>
+            <Label Content="Installing update" HorizontalAlignment="Center" Foreground="White" FontSize="20" />
+            <ProgressBar Margin="0,20,0,0" Height="20" Width="200" Value="{Binding ProgressValue}" />
         </StackPanel>
     </Grid>
-</Window>
+</Window>

+ 5 - 6
PixiEditor.UpdateInstaller/MainWindow.xaml.cs

@@ -1,16 +1,15 @@
 using System;
 using System.Diagnostics;
 using System.IO;
-using System.Reflection;
 using System.Threading.Tasks;
 using System.Windows;
 
 namespace PixiEditor.UpdateInstaller
 {
     /// <summary>
-    /// Interaction logic for MainWindow.xaml
+    ///     Interaction logic for MainWindow.xaml.
     /// </summary>
-    public partial class MainWindow : Window
+    public partial class MainWindow
     {
         public MainWindow()
         {
@@ -20,14 +19,14 @@ namespace PixiEditor.UpdateInstaller
 
         private async void Window_Loaded(object sender, RoutedEventArgs e)
         {
-            ViewModelMain vmm = ((ViewModelMain)DataContext);
+            ViewModelMain vmm = (ViewModelMain)DataContext;
             await Task.Run(() =>
             {
                 try
                 {
                     vmm.InstallUpdate();
                 }
-                catch(Exception ex)
+                catch (Exception ex)
                 {
                     MessageBox.Show(ex.Message, "Update error", MessageBoxButton.OK, MessageBoxImage.Error);
                     File.AppendAllText("ErrorLog.txt", $"Error PixiEditor.UpdateInstaller: {DateTime.Now}\n{ex.Message}\n{ex.StackTrace}\n-----\n");
@@ -41,4 +40,4 @@ namespace PixiEditor.UpdateInstaller
             Close();
         }
     }
-}
+}

+ 5 - 5
PixiEditor.UpdateInstaller/ViewModelBase.cs

@@ -1,17 +1,17 @@
 using System.ComponentModel;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
 
 namespace PixiEditor.UpdateInstaller
 {
     public class ViewModelBase : INotifyPropertyChanged
     {
-        public event PropertyChangedEventHandler PropertyChanged = delegate { };
+        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
 
         protected void RaisePropertyChanged(string property)
         {
-            if (property != null) PropertyChanged(this, new PropertyChangedEventArgs(property));
+            if (property != null)
+            {
+                PropertyChanged(this, new PropertyChangedEventArgs(property));
+            }
         }
     }
 }

+ 22 - 28
PixiEditor.UpdateInstaller/ViewModelMain.cs

@@ -1,35 +1,13 @@
-using PixiEditor.UpdateModule;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
+using System;
 using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
+using PixiEditor.UpdateModule;
 
 namespace PixiEditor.UpdateInstaller
 {
     public class ViewModelMain : ViewModelBase
-    {
-        public ViewModelMain Current { get; private set; }
-        public UpdateModule.UpdateInstaller Installer { get; set; }
-
-        public string UpdateDirectory { get; private set; }
-
-        private float _progressValue;
-
-        public float ProgressValue
-        {
-            get => _progressValue;
-            set 
-            { 
-                _progressValue = value;
-                RaisePropertyChanged(nameof(ProgressValue));
-            }
-        }
-
+    {
+        private float progressValue;
+
         public ViewModelMain()
         {
             Current = this;
@@ -42,6 +20,22 @@ namespace PixiEditor.UpdateInstaller
             UpdateDirectory = updateDirectory;
         }
 
+        public ViewModelMain Current { get; private set; }
+
+        public UpdateModule.UpdateInstaller Installer { get; set; }
+
+        public string UpdateDirectory { get; private set; }
+
+        public float ProgressValue
+        {
+            get => progressValue;
+            set
+            {
+                progressValue = value;
+                RaisePropertyChanged(nameof(ProgressValue));
+            }
+        }
+
         public void InstallUpdate()
         {
             string[] files = Directory.GetFiles(UpdateDownloader.DownloadLocation, "update-*.zip");
@@ -63,4 +57,4 @@ namespace PixiEditor.UpdateInstaller
             ProgressValue = e.Progress;
         }
     }
-}
+}

+ 4 - 5
PixiEditor.UpdateModule/Asset.cs

@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
 namespace PixiEditor.UpdateModule
 {
@@ -9,9 +6,11 @@ namespace PixiEditor.UpdateModule
     {
         [JsonPropertyName("url")]
         public string Url { get; set; }
+
         [JsonPropertyName("name")]
         public string Name { get; set; }
+
         [JsonPropertyName("content_type")]
         public string ContentType { get; set; }
     }
-}
+}

+ 15 - 11
PixiEditor.UpdateModule/ReleaseInfo.cs

@@ -1,26 +1,30 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
 namespace PixiEditor.UpdateModule
 {
     public class ReleaseInfo
     {
+        public ReleaseInfo()
+        {
+        }
+
+        public ReleaseInfo(bool dataFetchSuccessful)
+        {
+            WasDataFetchSuccessful = dataFetchSuccessful;
+        }
+
         [JsonPropertyName("tag_name")]
         public string TagName { get; set; }
+
         [JsonPropertyName("draft")]
         public bool IsDraft { get; set; }
+
         [JsonPropertyName("prerelease")]
         public bool IsPrerelease { get; set; }
+
         [JsonPropertyName("assets")]
         public Asset[] Assets { get; set; }
-        public bool WasDataFetchSuccessfull { get; set; } = true;
 
-        public ReleaseInfo() { }
-        public ReleaseInfo(bool dataFetchSuccessfull)
-        {
-            WasDataFetchSuccessfull = dataFetchSuccessfull;
-        }
+        public bool WasDataFetchSuccessful { get; set; } = true;
     }
-}
+}

+ 34 - 29
PixiEditor.UpdateModule/UpdateChecker.cs

@@ -1,5 +1,5 @@
-using System;
-using System.Globalization;
+using System.Globalization;
+using System.Net;
 using System.Net.Http;
 using System.Text.Json;
 using System.Threading.Tasks;
@@ -8,63 +8,68 @@ namespace PixiEditor.UpdateModule
 {
     public class UpdateChecker
     {
-        public const string ReleaseApiUrl = "https://api.github.com/repos/PixiEditor/PixiEditor/releases/latest";
-        public string CurrentVersionTag { get; set; }
-
-        public ReleaseInfo LatestReleaseInfo { get; set; }
+        private const string ReleaseApiUrl = "https://api.github.com/repos/PixiEditor/PixiEditor/releases/latest";
 
         public UpdateChecker(string currentVersionTag)
         {
             CurrentVersionTag = currentVersionTag;
         }
 
-        public async Task<bool> CheckUpdateAvailable()
-        {
-            LatestReleaseInfo = await GetLatestReleaseInfo();
-            return CheckUpdateAvailable(LatestReleaseInfo);
-        }
+        public ReleaseInfo LatestReleaseInfo { get; private set; }
 
-        public bool CheckUpdateAvailable(ReleaseInfo latestRelease)
-        {          
-            return latestRelease.WasDataFetchSuccessfull && VersionBigger(CurrentVersionTag, latestRelease.TagName);
-        }
+        private string CurrentVersionTag { get; }
 
         /// <summary>
-        /// Compares version strings and returns true if newVer > originalVer
+        ///     Compares version strings and returns true if newVer > originalVer.
         /// </summary>
-        /// <param name="originalVer"></param>
+        /// <param name="originalVer" />
         /// <param name="newVer"></param>
         /// <returns></returns>
         public static bool VersionBigger(string originalVer, string newVer)
         {
-            if(ParseVersionString(originalVer, out float ver1))
+            if (!ParseVersionString(originalVer, out float ver1))
             {
-                if (ParseVersionString(newVer, out float ver2))
-                {
-                    return ver2 > ver1;
-                }
+                return false;
+            }
+
+            if (ParseVersionString(newVer, out float ver2))
+            {
+                return ver2 > ver1;
             }
+
             return false;
         }
 
-        private static bool ParseVersionString(string versionString, out float version)
+        public async Task<bool> CheckUpdateAvailable()
+        {
+            LatestReleaseInfo = await GetLatestReleaseInfo_Async();
+            return CheckUpdateAvailable(LatestReleaseInfo);
+        }
+
+        public bool CheckUpdateAvailable(ReleaseInfo latestRelease)
         {
-            return float.TryParse(versionString.Replace(".", "").Insert(1, "."), NumberStyles.Any, CultureInfo.InvariantCulture, out version);
+            return latestRelease.WasDataFetchSuccessful && VersionBigger(CurrentVersionTag, latestRelease.TagName);
         }
 
-        public async Task<ReleaseInfo> GetLatestReleaseInfo()
+        private static async Task<ReleaseInfo> GetLatestReleaseInfo_Async()
         {
-            using(HttpClient client = new HttpClient())
+            using (HttpClient client = new HttpClient())
             {
                 client.DefaultRequestHeaders.Add("User-Agent", "PixiEditor");
-                var response = await client.GetAsync(ReleaseApiUrl);
-                if(response.StatusCode == System.Net.HttpStatusCode.OK)
+                HttpResponseMessage response = await client.GetAsync(ReleaseApiUrl);
+                if (response.StatusCode == HttpStatusCode.OK)
                 {
                     string content = await response.Content.ReadAsStringAsync();
                     return JsonSerializer.Deserialize<ReleaseInfo>(content);
                 }
             }
+
             return new ReleaseInfo(false);
         }
+
+        private static bool ParseVersionString(string versionString, out float version)
+        {
+            return float.TryParse(versionString.Replace(".", string.Empty).Insert(1, "."), NumberStyles.Any, CultureInfo.InvariantCulture, out version);
+        }
     }
-}
+}

+ 3 - 2
PixiEditor.UpdateModule/UpdateDownloader.cs

@@ -9,7 +9,8 @@ namespace PixiEditor.UpdateModule
 {
     public static class UpdateDownloader
     {
-        public static string DownloadLocation = Path.Join(Path.GetTempPath(), "PixiEditor");
+        public static string DownloadLocation { get; } = Path.Join(Path.GetTempPath(), "PixiEditor");
+
         public static async Task DownloadReleaseZip(ReleaseInfo release)
         {
             Asset matchingAsset = GetMatchingAsset(release);
@@ -43,4 +44,4 @@ namespace PixiEditor.UpdateModule
             && x.Name.Contains(arch));
         }
     }
-}
+}

+ 20 - 14
PixiEditor.UpdateModule/UpdateInstaller.cs

@@ -8,28 +8,33 @@ namespace PixiEditor.UpdateModule
     public class UpdateInstaller
     {
         public const string TargetDirectoryName = "UpdateFiles";
-        public static string UpdateFilesPath = Path.Join(UpdateDownloader.DownloadLocation, TargetDirectoryName);
 
+        private float progress = 0;
+
+        public UpdateInstaller(string archiveFileName, string targetDirectory)
+        {
+            ArchiveFileName = archiveFileName;
+            TargetDirectory = targetDirectory;
+        }
+
         public event EventHandler<UpdateProgressChangedEventArgs> ProgressChanged;
-        private float _progress = 0;
-        public float Progress 
+
+        public static string UpdateFilesPath { get; set; } = Path.Join(UpdateDownloader.DownloadLocation, TargetDirectoryName);
+
+        public float Progress
         {
-            get => _progress;
+            get => progress;
             set
             {
-                _progress = value;
+                progress = value;
                 ProgressChanged?.Invoke(this, new UpdateProgressChangedEventArgs(value));
             }
         }
+
         public string ArchiveFileName { get; set; }
+
         public string TargetDirectory { get; set; }
 
-        public UpdateInstaller(string archiveFileName, string targetDirectory)
-        {
-            ArchiveFileName = archiveFileName;
-            TargetDirectory = targetDirectory;
-        }
-
         public void Install()
         {
             var processes = Process.GetProcessesByName("PixiEditor");
@@ -37,8 +42,9 @@ namespace PixiEditor.UpdateModule
             {
                 processes[0].WaitForExit();
             }
+
             ZipFile.ExtractToDirectory(ArchiveFileName, UpdateFilesPath, true);
-            Progress = 25; //25% for unzip
+            Progress = 25; // 25% for unzip
             string dirWithFiles = Directory.GetDirectories(UpdateFilesPath)[0];
             string[] files = Directory.GetFiles(dirWithFiles);
             CopyFilesToDestination(files);
@@ -54,7 +60,7 @@ namespace PixiEditor.UpdateModule
 
         private void CopyFilesToDestination(string[] files)
         {
-            float fileCopiedVal = 74f / files.Length; //74% is reserved for copying
+            float fileCopiedVal = 74f / files.Length; // 74% is reserved for copying
             string destinationDir = TargetDirectory;
             foreach (string file in files)
             {
@@ -64,4 +70,4 @@ namespace PixiEditor.UpdateModule
             }
         }
     }
-}
+}

+ 2 - 2
PixiEditor.UpdateModule/UpdateProgressChangedEventArgs.cs

@@ -4,11 +4,11 @@ namespace PixiEditor.UpdateModule
 {
     public class UpdateProgressChangedEventArgs : EventArgs
     {
-        public float Progress { get; set; }
-
         public UpdateProgressChangedEventArgs(float progress)
         {
             Progress = progress;
         }
+
+        public float Progress { get; set; }
     }
 }

+ 50 - 0
PixiEditor.sln

@@ -0,0 +1,50 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28729.10
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor", "PixiEditor\PixiEditor.csproj", "{2CCDDE79-06CB-4771-AF85-7B25313EBA30}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.UpdateInstaller", "PixiEditor.UpdateInstaller\PixiEditor.UpdateInstaller.csproj", "{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.UpdateModule", "PixiEditor.UpdateModule\PixiEditor.UpdateModule.csproj", "{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditorTests", "PixiEditorTests\PixiEditorTests.csproj", "{5193C1C1-8362-40FD-802B-E097E8C88082}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildConfiguration", "BuildConfiguration", "{9337E60D-8425-4E87-950C-F07A09518081}"
+ProjectSection(SolutionItems) = preProject
+	stylecop.json = stylecop.json
+	Custom.ruleset = Custom.ruleset
+	Directory.Build.props = Directory.Build.props
+EndProjectSection
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|Any CPU.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {D04B4AB0-CA33-42FD-A909-79966F9255C5}
+	EndGlobalSection
+EndGlobal

+ 4 - 2
PixiEditor/Exceptions/LengthMismatchException.cs → PixiEditor/Exceptions/ArrayLengthMismatchException.cs

@@ -6,11 +6,13 @@ namespace PixiEditor.Exceptions
     {
         public const string DefaultMessage = "First array length doesn't match second array length";
 
-        public ArrayLengthMismatchException() : base(DefaultMessage)
+        public ArrayLengthMismatchException()
+            : base(DefaultMessage)
         {
         }
 
-        public ArrayLengthMismatchException(string message) : base(message)
+        public ArrayLengthMismatchException(string message)
+            : base(message)
         {
         }
     }

+ 105 - 93
PixiEditor/Helpers/Behaviours/AllowableCharactersTextBoxBehavior.cs

@@ -1,94 +1,106 @@
-using System;
-using System.Text.RegularExpressions;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using System.Windows.Interactivity;
-
-namespace PixiEditor.Helpers.Behaviours
-{
-    public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
-    {
-        public static readonly DependencyProperty RegularExpressionProperty =
-            DependencyProperty.Register("RegularExpression", typeof(string), typeof(AllowableCharactersTextBoxBehavior),
-                new FrameworkPropertyMetadata(".*"));
-
-        public static readonly DependencyProperty MaxLengthProperty =
-            DependencyProperty.Register("MaxLength", typeof(int), typeof(AllowableCharactersTextBoxBehavior),
-                new FrameworkPropertyMetadata(int.MinValue));
-
-        public string RegularExpression
-        {
-            get => (string) GetValue(RegularExpressionProperty);
-            set => SetValue(RegularExpressionProperty, value);
-        }
-
-        public int MaxLength
-        {
-            get => (int) GetValue(MaxLengthProperty);
-            set => SetValue(MaxLengthProperty, value);
-        }
-
-        protected override void OnAttached()
-        {
-            base.OnAttached();
-            AssociatedObject.PreviewTextInput += OnPreviewTextInput;
-            DataObject.AddPastingHandler(AssociatedObject, OnPaste);
-        }
-
-        private void OnPaste(object sender, DataObjectPastingEventArgs e)
-        {
-            if (e.DataObject.GetDataPresent(DataFormats.Text))
-            {
-                string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
-
-                if (!IsValid(text, true)) e.CancelCommand();
-            }
-            else
-            {
-                e.CancelCommand();
-            }
-        }
-
-        private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
-        {
-            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);
-        }
-
-        private bool ExceedsMaxLength(string newText, bool paste)
-        {
-            if (MaxLength == 0) return false;
-
-            return LengthOfModifiedText(newText, paste) > MaxLength;
-        }
-
-        private int LengthOfModifiedText(string newText, bool paste)
-        {
-            var countOfSelectedChars = AssociatedObject.SelectedText.Length;
-            var caretIndex = AssociatedObject.CaretIndex;
-            string text = AssociatedObject.Text;
-
-            if (countOfSelectedChars > 0 || paste)
-            {
-                text = text.Remove(caretIndex, countOfSelectedChars);
-                return text.Length + newText.Length;
-            }
-
-            var insert = Keyboard.IsKeyToggled(Key.Insert);
-
-            return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
-        }
-    }
+using System;
+using System.Text.RegularExpressions;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Interactivity;
+
+namespace PixiEditor.Helpers.Behaviours
+{
+    public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
+    {
+        public static readonly DependencyProperty RegularExpressionProperty =
+            DependencyProperty.Register(
+                "RegularExpression",
+                typeof(string),
+                typeof(AllowableCharactersTextBoxBehavior),
+                new FrameworkPropertyMetadata(".*"));
+
+        public static readonly DependencyProperty MaxLengthProperty =
+            DependencyProperty.Register(
+                "MaxLength",
+                typeof(int),
+                typeof(AllowableCharactersTextBoxBehavior),
+                new FrameworkPropertyMetadata(int.MinValue));
+
+        public string RegularExpression
+        {
+            get => (string)GetValue(RegularExpressionProperty);
+            set => SetValue(RegularExpressionProperty, value);
+        }
+
+        public int MaxLength
+        {
+            get => (int)GetValue(MaxLengthProperty);
+            set => SetValue(MaxLengthProperty, value);
+        }
+
+        protected override void OnAttached()
+        {
+            base.OnAttached();
+            AssociatedObject.PreviewTextInput += OnPreviewTextInput;
+            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))
+            {
+                string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
+
+                if (!IsValid(text, true))
+                {
+                    e.CancelCommand();
+                }
+            }
+            else
+            {
+                e.CancelCommand();
+            }
+        }
+
+        private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
+        {
+            e.Handled = !IsValid(e.Text, false);
+        }
+
+        private bool IsValid(string newText, bool paste)
+        {
+            return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(newText, RegularExpression);
+        }
+
+        private bool ExceedsMaxLength(string newText, bool paste)
+        {
+            if (MaxLength == 0)
+            {
+                return false;
+            }
+
+            return LengthOfModifiedText(newText, paste) > MaxLength;
+        }
+
+        private int LengthOfModifiedText(string newText, bool paste)
+        {
+            int countOfSelectedChars = AssociatedObject.SelectedText.Length;
+            int caretIndex = AssociatedObject.CaretIndex;
+            string text = AssociatedObject.Text;
+
+            if (countOfSelectedChars > 0 || paste)
+            {
+                text = text.Remove(caretIndex, countOfSelectedChars);
+                return text.Length + newText.Length;
+            }
+
+            bool insert = Keyboard.IsKeyToggled(Key.Insert);
+
+            return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
+        }
+    }
 }

+ 71 - 63
PixiEditor/Helpers/Behaviours/HintTextBehavior.cs

@@ -1,64 +1,72 @@
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Interactivity;
-using System.Windows.Media;
-
-namespace PixiEditor.Helpers.Behaviours
-{
-    internal class HintTextBehavior : Behavior<TextBox>
-    {
-        // Using a DependencyProperty as the backing store for Hint.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty HintProperty =
-            DependencyProperty.Register("Hint", typeof(string), typeof(HintTextBehavior),
-                new PropertyMetadata(string.Empty));
-
-        private Brush _textColor;
-
-        public string Hint
-        {
-            get => (string) GetValue(HintProperty);
-            set => SetValue(HintProperty, value);
-        }
-
-
-        protected override void OnAttached()
-        {
-            base.OnAttached();
-            AssociatedObject.GotFocus += AssociatedObject_GotFocus;
-            AssociatedObject.LostFocus += AssociatedObject_LostFocus;
-            _textColor = AssociatedObject.Foreground;
-            SetHint(true);
-        }
-
-        private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
-        {
-            if (string.IsNullOrEmpty(AssociatedObject.Text)) SetHint(true);
-        }
-
-        private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
-        {
-            if (AssociatedObject.Text == Hint) SetHint(false);
-        }
-
-        private void SetHint(bool active)
-        {
-            if (active)
-            {
-                AssociatedObject.Foreground = (SolidColorBrush) new BrushConverter().ConvertFromString("#7B7B7B");
-                AssociatedObject.Text = Hint;
-            }
-            else
-            {
-                AssociatedObject.Text = string.Empty;
-                AssociatedObject.Foreground = _textColor;
-            }
-        }
-
-        protected override void OnDetaching()
-        {
-            base.OnDetaching();
-            AssociatedObject.LostFocus -= AssociatedObject_LostFocus;
-            AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
-        }
-    }
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Interactivity;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Behaviours
+{
+    internal class HintTextBehavior : Behavior<TextBox>
+    {
+        // Using a DependencyProperty as the backing store for Hint.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty HintProperty =
+            DependencyProperty.Register(
+                "Hint",
+                typeof(string),
+                typeof(HintTextBehavior),
+                new PropertyMetadata(string.Empty));
+
+        private Brush textColor;
+
+        public string Hint
+        {
+            get => (string)GetValue(HintProperty);
+            set => SetValue(HintProperty, value);
+        }
+
+        protected override void OnAttached()
+        {
+            base.OnAttached();
+            AssociatedObject.GotFocus += AssociatedObject_GotFocus;
+            AssociatedObject.LostFocus += AssociatedObject_LostFocus;
+            textColor = AssociatedObject.Foreground;
+            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))
+            {
+                SetHint(true);
+            }
+        }
+
+        private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
+        {
+            if (AssociatedObject.Text == Hint)
+            {
+                SetHint(false);
+            }
+        }
+
+        private void SetHint(bool active)
+        {
+            if (active)
+            {
+                AssociatedObject.Foreground = (SolidColorBrush)new BrushConverter().ConvertFromString("#7B7B7B");
+                AssociatedObject.Text = Hint;
+            }
+            else
+            {
+                AssociatedObject.Text = string.Empty;
+                AssociatedObject.Foreground = textColor;
+            }
+        }
+    }
 }

+ 62 - 57
PixiEditor/Helpers/Behaviours/MouseBehaviour.cs

@@ -1,58 +1,63 @@
-using System.Windows;
-using System.Windows.Input;
-using System.Windows.Interactivity;
-
-namespace PixiEditor.Helpers.Behaviours
-{
-    public class MouseBehaviour : Behavior<FrameworkElement>
-    {
-        public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
-            "MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
-
-        public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
-            "MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
-
-        // Using a DependencyProperty as the backing store for RelativeTo.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty RelativeToProperty =
-            DependencyProperty.Register("RelativeTo", typeof(FrameworkElement), typeof(MouseBehaviour),
-                new PropertyMetadata(default(FrameworkElement)));
-
-        public double MouseY
-        {
-            get => (double) GetValue(MouseYProperty);
-            set => SetValue(MouseYProperty, value);
-        }
-
-        public double MouseX
-        {
-            get => (double) GetValue(MouseXProperty);
-            set => SetValue(MouseXProperty, value);
-        }
-
-
-        public FrameworkElement RelativeTo
-        {
-            get => (FrameworkElement) GetValue(RelativeToProperty);
-            set => SetValue(RelativeToProperty, value);
-        }
-
-
-        protected override void OnAttached()
-        {
-            AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
-        }
-
-        private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
-        {
-            if (RelativeTo == null) RelativeTo = AssociatedObject;
-            var pos = mouseEventArgs.GetPosition(RelativeTo);
-            MouseX = pos.X;
-            MouseY = pos.Y;
-        }
-
-        protected override void OnDetaching()
-        {
-            AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
-        }
-    }
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Interactivity;
+
+namespace PixiEditor.Helpers.Behaviours
+{
+    public class MouseBehaviour : Behavior<FrameworkElement>
+    {
+        public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
+            "MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
+
+        public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
+            "MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
+
+        // Using a DependencyProperty as the backing store for RelativeTo.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty RelativeToProperty =
+            DependencyProperty.Register(
+                "RelativeTo",
+                typeof(FrameworkElement),
+                typeof(MouseBehaviour),
+                new PropertyMetadata(default(FrameworkElement)));
+
+        public double MouseY
+        {
+            get => (double)GetValue(MouseYProperty);
+            set => SetValue(MouseYProperty, value);
+        }
+
+        public double MouseX
+        {
+            get => (double)GetValue(MouseXProperty);
+            set => SetValue(MouseXProperty, value);
+        }
+
+        public FrameworkElement RelativeTo
+        {
+            get => (FrameworkElement)GetValue(RelativeToProperty);
+            set => SetValue(RelativeToProperty, value);
+        }
+
+        protected override void OnAttached()
+        {
+            AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
+        }
+
+        protected override void OnDetaching()
+        {
+            AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
+        }
+
+        private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
+        {
+            if (RelativeTo == null)
+            {
+                RelativeTo = AssociatedObject;
+            }
+
+            Point pos = mouseEventArgs.GetPosition(RelativeTo);
+            MouseX = pos.X;
+            MouseY = pos.Y;
+        }
+    }
 }

+ 116 - 99
PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -1,100 +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));
-
-
-        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)
-        {
-            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()
-        {
-            if (_valueConverted || FillSize == false) return;
-
-            if (int.TryParse(Regex.Replace(AssociatedObject.Text, "\\p{L}", ""), out int result) && result > 0)
-                AssociatedObject.Text = $"{AssociatedObject.Text} px";
-            else //If text in textbox isn't number, set it to old value
-                AssociatedObject.Text = _oldText;
-            _valueConverted = true;
-        }
-    }
+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));
+
+        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));
+        }
+
+        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}", string.Empty), out int result) && result > 0)
+            {
+                AssociatedObject.Text = $"{AssociatedObject.Text} px";
+            }
+
+            // If text in textbox isn't number, set it to old value
+            else
+            {
+                AssociatedObject.Text = oldText;
+            }
+
+            valueConverted = true;
+        }
+    }
 }

+ 5 - 0
PixiEditor/Helpers/Converters/BoolToColorConverter.cs

@@ -14,8 +14,13 @@ namespace PixiEditor.Helpers.Converters
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
             if (value is bool boolean)
+            {
                 if (boolean == false)
+                {
                     return "Transparent";
+                }
+            }
+
             return "#638DCA";
         }
     }

+ 5 - 0
PixiEditor/Helpers/Converters/BoolToIntConverter.cs

@@ -14,8 +14,13 @@ namespace PixiEditor.Helpers.Converters
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
             if (value is bool boolean)
+            {
                 if (boolean == false)
+                {
                     return 0;
+                }
+            }
+
             return 1;
         }
     }

+ 3 - 5
PixiEditor/Helpers/Converters/DoubleToIntConverter.cs

@@ -1,7 +1,5 @@
 using System;
-using System.Collections.Generic;
 using System.Globalization;
-using System.Text;
 using System.Windows.Data;
 
 namespace PixiEditor.Helpers.Converters
@@ -12,8 +10,8 @@ namespace PixiEditor.Helpers.Converters
         {
             if (value is double || value is float)
             {
-                double val = (double) value;
-                return (int) val;
+                double val = (double)value;
+                return (int)val;
             }
 
             return value;
@@ -24,4 +22,4 @@ namespace PixiEditor.Helpers.Converters
             throw new NotImplementedException();
         }
     }
-}
+}

+ 9 - 7
PixiEditor/Helpers/Converters/FloatNormalizeConverter.cs

@@ -1,17 +1,19 @@
 using System;
-using System.Collections.Generic;
 using System.Globalization;
-using System.Text;
 using System.Windows.Data;
 
 namespace PixiEditor.Helpers.Converters
 {
     public class FloatNormalizeConverter : IValueConverter
     {
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => 
-            (float)value * 100;
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return (float)value * 100;
+        }
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => 
-            (float) value / 100;
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return (float)value / 100;
+        }
     }
-}
+}

+ 9 - 2
PixiEditor/Helpers/Converters/OppositeVisibilityConverter.cs

@@ -9,7 +9,11 @@ namespace PixiEditor.Helpers.Converters
     {
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
-            if (value.ToString().ToLower() == "visible") return Visibility.Hidden;
+            if (value.ToString().ToLower() == "visible")
+            {
+                return Visibility.Hidden;
+            }
+
             return Visibility.Visible;
         }
 
@@ -17,8 +21,11 @@ namespace PixiEditor.Helpers.Converters
         {
             if (value is Visibility)
             {
-                if ((Visibility) value == Visibility.Visible)
+                if ((Visibility)value == Visibility.Visible)
+                {
                     return "Hidden";
+                }
+
                 return "Visible";
             }
 

+ 33 - 25
PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs

@@ -1,26 +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)
-        {
-            if (string.IsNullOrWhiteSpace(value as string)) return null;
-            string slicedString = value.ToString().Split(' ').First();
-            slicedString = Regex.Replace(slicedString, "\\p{L}", "");
-            if (slicedString == "") return null;
-            return int.Parse(slicedString);
-        }
-    }
+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}", string.Empty);
+            if (slicedString == string.Empty)
+            {
+                return null;
+            }
+
+            return int.Parse(slicedString);
+        }
+    }
 }

+ 29 - 20
PixiEditor/Helpers/Extensions/DictionaryHelper.cs

@@ -1,21 +1,30 @@
-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 (var item in dictToAdd) dict[item.Key] = item.Value;
-        }
-
-        public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dict,
-            IDictionary<TKey, TValue> dictToAdd)
-        {
-            foreach (var item in dictToAdd)
-                if (!dict.ContainsKey(item.Key))
-                    dict.Add(item.Key, item.Value);
-        }
-    }
+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)
+        {
+            foreach (KeyValuePair<TKey, TValue> item in dictToAdd)
+            {
+                if (!dict.ContainsKey(item.Key))
+                {
+                    dict.Add(item.Key, item.Value);
+                }
+            }
+        }
+    }
 }

+ 55 - 45
PixiEditor/Helpers/GlobalMouseHook.cs

@@ -4,18 +4,26 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 using System.Windows;
+using System.Windows.Input;
 
 namespace PixiEditor.Helpers
 {
+    public delegate void MouseUpEventHandler(object sender, Point p, MouseButton button);
+
     // see https://stackoverflow.com/questions/22659925/how-to-capture-mouseup-event-outside-the-wpf-window
     [ExcludeFromCodeCoverage]
     public static class GlobalMouseHook
-    {
-        private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
-        private static int _mouseHookHandle;
-        private static HookProc _mouseDelegate;
+    {
+        private const int WH_MOUSE_LL = 14;
+        private const int WM_LBUTTONUP = 0x0202;
+        private const int WM_MBUTTONUP = 0x0208;
+        private const int WM_RBUTTONUP = 0x0205;
 
-        private static event MouseUpEventHandler MouseUp;
+        private static int mouseHookHandle;
+        private static HookProc mouseDelegate;
+
+        private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
+
         public static event MouseUpEventHandler OnMouseUp
         {
             add
@@ -23,6 +31,7 @@ namespace PixiEditor.Helpers
                 Subscribe();
                 MouseUp += value;
             }
+
             remove
             {
                 MouseUp -= value;
@@ -30,18 +39,20 @@ namespace PixiEditor.Helpers
             }
         }
 
+        private static event MouseUpEventHandler MouseUp;
+
         public static void RaiseMouseUp()
         {
-            MouseUp?.Invoke(default, default);
+            MouseUp?.Invoke(default, default, default);
         }
 
         private static void Unsubscribe()
         {
-            if (_mouseHookHandle != 0)
+            if (mouseHookHandle != 0)
             {
-                int result = UnhookWindowsHookEx(_mouseHookHandle);
-                _mouseHookHandle = 0;
-                _mouseDelegate = null;
+                int result = UnhookWindowsHookEx(mouseHookHandle);
+                mouseHookHandle = 0;
+                mouseDelegate = null;
                 if (result == 0)
                 {
                     int errorCode = Marshal.GetLastWin32Error();
@@ -52,15 +63,15 @@ namespace PixiEditor.Helpers
 
         private static void Subscribe()
         {
-            if (_mouseHookHandle == 0)
+            if (mouseHookHandle == 0)
             {
-                _mouseDelegate = MouseHookProc;
-                _mouseHookHandle = SetWindowsHookEx(
+                mouseDelegate = MouseHookProc;
+                mouseHookHandle = SetWindowsHookEx(
                     WH_MOUSE_LL,
-                    _mouseDelegate,
+                    mouseDelegate,
                     GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
                     0);
-                if (_mouseHookHandle == 0)
+                if (mouseHookHandle == 0)
                 {
                     int errorCode = Marshal.GetLastWin32Error();
                     throw new Win32Exception(errorCode);
@@ -73,40 +84,24 @@ namespace PixiEditor.Helpers
             if (nCode >= 0)
             {
                 MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
-                if (wParam == WM_LBUTTONUP)
+                if (wParam == WM_LBUTTONUP || wParam == WM_MBUTTONUP || wParam == WM_RBUTTONUP)
                 {
                     if (MouseUp != null)
                     {
-                        MouseUp.Invoke(null, new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y));
+                        MouseButton button = wParam == WM_LBUTTONUP ? MouseButton.Left
+                            : wParam == WM_MBUTTONUP ? MouseButton.Middle : MouseButton.Right;
+                        MouseUp.Invoke(null, new Point(mouseHookStruct.Pt.X, mouseHookStruct.Pt.Y), button);
                     }
                 }
             }
-            return CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
-        }
-
-        private const int WH_MOUSE_LL = 14;
-        private const int WM_LBUTTONUP = 0x0202;
-
-        [StructLayout(LayoutKind.Sequential)]
-        private struct POINT
-        {
-            public int x;
-            public int y;
-        }
-
-        [StructLayout(LayoutKind.Sequential)]
-        private struct MSLLHOOKSTRUCT
-        {
-            public POINT pt;
-            public uint mouseData;
-            public uint flags;
-            public uint time;
-            public IntPtr dwExtraInfo;
+
+            return CallNextHookEx(mouseHookHandle, nCode, wParam, lParam);
         }
 
-        [DllImport("user32.dll", 
+        [DllImport(
+            "user32.dll",
             CharSet = CharSet.Auto,
-            CallingConvention = CallingConvention.StdCall, 
+            CallingConvention = CallingConvention.StdCall,
             SetLastError = true)]
         private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
 
@@ -118,14 +113,29 @@ namespace PixiEditor.Helpers
         private static extern int UnhookWindowsHookEx(int idHook);
 
         [DllImport(
-            "user32.dll", 
+            "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 static extern IntPtr GetModuleHandle(string name);
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct POINT
+        {
+            public int X;
+            public int Y;
+        }
 
-    public delegate void MouseUpEventHandler(object sender, Point p);
-}
+        [StructLayout(LayoutKind.Sequential)]
+        private struct MSLLHOOKSTRUCT
+        {
+            public POINT Pt;
+            public uint MouseData;
+            public uint Flags;
+            public uint Time;
+            public IntPtr DwExtraInfo;
+        }
+    }
+}

+ 42 - 52
PixiEditor/Helpers/RelayCommand.cs

@@ -1,53 +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)
-        {
-            if (execute == null)
-                throw new ArgumentNullException("execute");
-
-            _execute = execute;
-            _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
-    }
+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;
+        }
+
+        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);
+        }
+    }
 }

+ 4 - 2
PixiEditor/Helpers/UI/ReversedOrderStackPanel.cs

@@ -10,14 +10,16 @@ namespace PixiEditor.Helpers.UI
         protected override Size ArrangeOverride(Size arrangeSize)
         {
             bool fHorizontal = Orientation == Orientation.Horizontal;
-            var rcChild = new Rect(arrangeSize);
+            Rect rcChild = new Rect(arrangeSize);
             double previousChildSize = 0.0;
 
-            var children = InternalChildren.Cast<UIElement>().Reverse();
+            System.Collections.Generic.IEnumerable<UIElement> children = InternalChildren.Cast<UIElement>().Reverse();
             foreach (UIElement child in children)
             {
                 if (child == null)
+                {
                     continue;
+                }
 
                 if (fHorizontal)
                 {

+ 1 - 1
PixiEditor/Helpers/Validators/SizeValidationRule.cs

@@ -7,7 +7,7 @@ namespace PixiEditor.Helpers.Validators
     {
         public override ValidationResult Validate(object value, CultureInfo cultureInfo)
         {
-            return new ValidationResult(int.Parse(((string) value).Split(' ')[0]) > 0, null); //Size is greater than 0
+            return new ValidationResult(int.Parse(((string)value).Split(' ')[0]) > 0, null); // Size is greater than 0
         }
     }
 }

BIN
PixiEditor/Images/MoveViewportImage.png


+ 154 - 137
PixiEditor/Models/Colors/ExColor.cs

@@ -1,138 +1,155 @@
-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;
-
-                if (dR == max)
-                    hue = (dG - dB) / 6 / delta;
-                else if (dG == max)
-                    hue = 1.0f / 3 + (dB - dR) / 6 / delta;
-                else
-                    hue = 2.0f / 3 + (dR - dG) / 6 / delta;
-
-                if (hue < 0)
-                    hue += 1;
-                if (hue > 1)
-                    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)
-        {
-            if (hue < 0)
-                hue += 1;
-
-            if (hue > 1)
-                hue -= 1;
-
-            if (6 * hue < 1)
-                return v1 + (v2 - v1) * 6 * hue;
-
-            if (2 * hue < 1)
-                return v2;
-
-            if (3 * hue < 2)
-                return v1 + (v2 - v1) * (2.0f / 3 - hue) * 6;
-
-            return v1;
-        }
-    }
+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);
+                }
+                else
+                {
+                    hue = (2.0f / 3) + ((dR - dG) / 6 / delta);
+                }
+
+                if (hue < 0)
+                {
+                    hue += 1;
+                }
+
+                if (hue > 1)
+                {
+                    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>
+        /// <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;
+            }
+
+            if (hue > 1)
+            {
+                hue -= 1;
+            }
+
+            if (6 * hue < 1)
+            {
+                return v1 + ((v2 - v1) * 6 * hue);
+            }
+
+            if (2 * hue < 1)
+            {
+                return v2;
+            }
+
+            if (3 * hue < 2)
+            {
+                return v1 + ((v2 - v1) * ((2.0f / 3) - hue) * 6);
+            }
+
+            return v1;
+        }
+    }
 }

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

@@ -0,0 +1,21 @@
+using System;
+using PixiEditor.Models.DataHolders;
+
+namespace PixiEditor.Models.Controllers
+{
+    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; }
+    }
+}

+ 121 - 102
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -12,30 +12,51 @@ using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
+using PixiEditor.Models.Tools.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;
+            MouseController.OnMouseDown += MouseController_OnMouseDown;
+            MouseController.OnMouseUp += MouseController_OnMouseUp;
+            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;
+            get => selectedTool;
             private set
             {
-                _selectedTool = value;
+                selectedTool = value;
                 RaisePropertyChanged("SelectedTool");
             }
         }
 
         public Layer PreviewLayer
         {
-            get => _previewLayer;
+            get => previewLayer;
             set
             {
-                _previewLayer = value;
+                previewLayer = value;
                 RaisePropertyChanged("PreviewLayer");
             }
         }
@@ -60,37 +81,28 @@ namespace PixiEditor.Models.Controllers
         }
 
         public BitmapOperationsUtility BitmapOperations { get; set; }
+
         public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
 
         public Document ActiveDocument
         {
-            get => _activeDocument;
+            get => activeDocument;
             set
             {
-                _activeDocument = value;
+                activeDocument = value;
                 RaisePropertyChanged("ActiveDocument");
                 DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value));
             }
-        }
-
-        private Document _activeDocument;
-
-        private Layer _previewLayer;
-        private Tool _selectedTool;
-
-        public BitmapManager()
+        }
+
+        /// <summary>
+        ///     Returns if tool is BitmapOperationTool.
+        /// </summary>
+        public static bool IsOperationTool(Tool tool)
         {
-            MouseController = new MouseMovementController();
-            MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
-            MouseController.MousePositionChanged += Controller_MousePositionChanged;
-            MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
-            BitmapOperations = new BitmapOperationsUtility(this);
-            ReadonlyToolUtility = new ReadonlyToolUtility();
+            return tool is BitmapOperationTool;
         }
 
-        public event EventHandler<LayersChangedEventArgs> LayersChanged;
-        public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
-
         public void SetActiveTool(Tool tool)
         {
             PreviewLayer = null;
@@ -101,8 +113,11 @@ namespace PixiEditor.Models.Controllers
 
         public void SetActiveLayer(int index)
         {
-            if (ActiveDocument.ActiveLayerIndex <= ActiveDocument.Layers.Count - 1)
-                ActiveDocument.ActiveLayer.IsActive = false;
+            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));
@@ -126,88 +141,128 @@ namespace PixiEditor.Models.Controllers
                 MaxHeight = ActiveDocument.Height,
                 MaxWidth = ActiveDocument.Width
             });
-            if (setAsActive) SetActiveLayer(ActiveDocument.Layers.Count - 1);
+            if (setAsActive)
+            {
+                SetActiveLayer(ActiveDocument.Layers.Count - 1);
+            }
+
             LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
         }
 
         public void RemoveLayer(int layerIndex)
         {
-            if (ActiveDocument.Layers.Count == 0) return;
-
+            if (ActiveDocument.Layers.Count == 0)
+            {
+                return;
+            }
+
             bool wasActive = ActiveDocument.Layers[layerIndex].IsActive;
             ActiveDocument.Layers.RemoveAt(layerIndex);
-            if (wasActive)
-                SetActiveLayer(0);
-            else if (ActiveDocument.ActiveLayerIndex > ActiveDocument.Layers.Count - 1)
-                SetActiveLayer(ActiveDocument.Layers.Count - 1);
+            if (wasActive)
+            {
+                SetActiveLayer(0);
+            }
+            else if (ActiveDocument.ActiveLayerIndex > ActiveDocument.Layers.Count - 1)
+            {
+                SetActiveLayer(ActiveDocument.Layers.Count - 1);
+            }
+        }
+
+        public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
+        {
+            if (SelectedTool.CanStartOutsideCanvas || clickedOnCanvas)
+            {
+                if (IsOperationTool(SelectedTool))
+                {
+                    BitmapOperations.ExecuteTool(newPosition, MouseController.LastMouseMoveCoordinates.ToList(), (BitmapOperationTool)SelectedTool);
+                }
+                else
+                {
+                    ReadonlyToolUtility.ExecuteTool(MouseController.LastMouseMoveCoordinates.ToArray(), (ReadonlyTool)SelectedTool);
+                }
+            }
+        }
+
+        public void GeneratePreviewLayer()
+        {
+            if (ActiveDocument != null)
+            {
+                PreviewLayer = new Layer("_previewLayer")
+                {
+                    MaxWidth = ActiveDocument.Width,
+                    MaxHeight = ActiveDocument.Height
+                };
+            }
+        }
+
+        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);   
+                ExecuteTool(e.NewPosition, MouseController.ClickedOnCanvas);
             }
             else if (Mouse.LeftButton == MouseButtonState.Released)
             {
                 HighlightPixels(e.NewPosition);
             }
+        }
+
+        private void MouseController_OnMouseDown(object sender, MouseEventArgs e)
+        {
+            SelectedTool.OnMouseDown(e);
         }
 
-        public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
+        private void MouseController_OnMouseUp(object sender, MouseEventArgs e)
         {
-            if (SelectedTool.CanStartOutsideCanvas || clickedOnCanvas)
-            {
-                if (IsOperationTool(SelectedTool))
-                {
-                    BitmapOperations.ExecuteTool(newPosition,
-                        MouseController.LastMouseMoveCoordinates.ToList(), (BitmapOperationTool)SelectedTool);
-                }
-                else
-                {
-                    ReadonlyToolUtility.ExecuteTool(MouseController.LastMouseMoveCoordinates.ToArray(),
-                        (ReadonlyTool)SelectedTool);
-                }
-            }
+            SelectedTool.OnMouseUp(e);
         }
 
         private bool IsDraggingViewport()
         {
-            return Keyboard.IsKeyDown(Key.LeftShift) && !(SelectedTool is ShapeTool);
+            return SelectedTool is MoveViewportTool;
         }
 
         private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
         {
-            SelectedTool.OnMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            SelectedTool.OnRecordingLeftMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
             PreviewLayer = null;
         }
 
         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()
-        {
-            if (ActiveDocument != null)
-                PreviewLayer = new Layer("_previewLayer")
-                {
-                    MaxWidth = ActiveDocument.Width,
-                    MaxHeight = ActiveDocument.Height
-                };
+            SelectedTool.OnStoppedRecordingMouseUp(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;
+            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);
+                PreviewLayer.Offset = new Thickness(highlightArea[0].X, highlightArea[0].Y, 0, 0);
             }
             else if (!IsInsideBounds(highlightArea))
             {
@@ -223,7 +278,7 @@ namespace PixiEditor.Models.Controllers
 
         private bool CanChangeHighlightOffset(Coordinates[] highlightArea)
         {
-            return highlightArea.Length > 0 && PreviewLayer != null && 
+            return highlightArea.Length > 0 && PreviewLayer != null &&
                    IsInsideBounds(highlightArea) && highlightArea.Length == PreviewLayer.Width * PreviewLayer.Height;
         }
 
@@ -233,41 +288,5 @@ namespace PixiEditor.Models.Controllers
                     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 int LayerAffected { get; set; }
-        public LayerAction LayerChangeType { get; set; }
-
-        public LayersChangedEventArgs(int layerAffected, LayerAction layerChangeType)
-        {
-            LayerAffected = layerAffected;
-            LayerChangeType = layerChangeType;
-        }
     }
 }

+ 60 - 51
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -14,10 +14,9 @@ namespace PixiEditor.Models.Controllers
 {
     public class BitmapOperationsUtility
     {
-        public BitmapManager Manager { get; set; }
-        private LayerChange[] _lastModifiedLayers;
+        private LayerChange[] lastModifiedLayers;
 
-        private Coordinates _lastMousePos;
+        private Coordinates lastMousePos;
 
         public BitmapOperationsUtility(BitmapManager manager)
         {
@@ -26,10 +25,12 @@ namespace PixiEditor.Models.Controllers
 
         public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
 
+        public BitmapManager Manager { get; set; }
+
         public void DeletePixels(Layer[] layers, Coordinates[] pixels)
         {
-            var changes = BitmapPixelChanges.FromSingleColoredArray(pixels, Color.FromArgb(0, 0, 0, 0));
-            var oldValues = BitmapUtils.GetPixelsForSelection(layers, pixels);
+            BitmapPixelChanges changes = BitmapPixelChanges.FromSingleColoredArray(pixels, Color.FromArgb(0, 0, 0, 0));
+            Dictionary<Layer, Color[]> oldValues = BitmapUtils.GetPixelsForSelection(layers, pixels);
             LayerChange[] old = new LayerChange[layers.Length];
             LayerChange[] newChange = new LayerChange[layers.Length];
             for (int i = 0; i < layers.Length; i++)
@@ -44,57 +45,71 @@ 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)
             {
-                if (Manager.ActiveDocument.Layers.Count == 0 || mouseMove.Count == 0) return;
+                if (Manager.ActiveDocument.Layers.Count == 0 || mouseMove.Count == 0)
+                {
+                    return;
+                }
+
                 mouseMove.Reverse();
                 UseTool(mouseMove, tool, Manager.PrimaryColor);
 
-                _lastMousePos = newPos;
+                lastMousePos = newPos;
             }
         }
 
         /// <summary>
-        ///     Applies pixels from preview layer to selected layer
+        ///     Applies pixels from preview layer to selected layer.
         /// </summary>
         public void StopAction()
         {
-            if (_lastModifiedLayers == null) return;
-            for (int i = 0; i < _lastModifiedLayers.Length; i++)
+            if (lastModifiedLayers == null)
+            {
+                return;
+            }
+
+            for (int i = 0; i < lastModifiedLayers.Length; i++)
             {
-                var layer = Manager.ActiveDocument.Layers[_lastModifiedLayers[i].LayerIndex];
+                Layer layer = Manager.ActiveDocument.Layers[lastModifiedLayers[i].LayerIndex];
 
-                BitmapPixelChanges oldValues = ApplyToLayer(layer, _lastModifiedLayers[i]).PixelChanges;
+                BitmapPixelChanges oldValues = ApplyToLayer(layer, lastModifiedLayers[i]).PixelChanges;
 
-               BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(_lastModifiedLayers[i].PixelChanges,
-                    oldValues, _lastModifiedLayers[i].LayerIndex));
-               Manager.PreviewLayer = null;
+                BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(
+                    lastModifiedLayers[i].PixelChanges,
+                    oldValues,
+                    lastModifiedLayers[i].LayerIndex));
+                Manager.PreviewLayer = null;
             }
         }
 
-
         private void UseTool(List<Coordinates> mouseMoveCords, BitmapOperationTool tool, Color color)
         {
             if (Keyboard.IsKeyDown(Key.LeftShift) && !MouseCordsNotInLine(mouseMoveCords))
+            {
                 mouseMoveCords = GetSquareCoordiantes(mouseMoveCords);
+            }
+
             if (!tool.RequiresPreviewLayer)
             {
                 LayerChange[] modifiedLayers = tool.Use(Manager.ActiveLayer, mouseMoveCords.ToArray(), color);
                 LayerChange[] oldPixelsValues = new LayerChange[modifiedLayers.Length];
                 for (int i = 0; i < modifiedLayers.Length; i++)
                 {
-                    var layer = Manager.ActiveDocument.Layers[modifiedLayers[i].LayerIndex];
+                    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
@@ -107,7 +122,7 @@ namespace PixiEditor.Models.Controllers
         {
             layer.DynamicResize(change.PixelChanges);
 
-            var oldPixelsValues = new LayerChange(
+            LayerChange oldPixelsValues = new LayerChange(
                 GetOldPixelsValues(change.PixelChanges.ChangedPixels.Keys.ToArray()),
                 change.LayerIndex);
 
@@ -123,14 +138,20 @@ 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;
             int yLength = mouseMoveCords[0].Y - mouseMoveCords[^1].Y;
-            if (mouseMoveCords[^1].Y > mouseMoveCords[0].Y) xLength *= -1;
-            if (mouseMoveCords[^1].X > mouseMoveCords[0].X) xLength *= -1;
+            if (mouseMoveCords[^1].Y > mouseMoveCords[0].Y)
+            {
+                xLength *= -1;
+            }
+
+            if (mouseMoveCords[^1].X > mouseMoveCords[0].X)
+            {
+                xLength *= -1;
+            }
+
             mouseMoveCords[0] = new Coordinates(mouseMoveCords[^1].X + xLength, mouseMoveCords[^1].Y + yLength);
             return mouseMoveCords;
         }
@@ -140,44 +161,32 @@ namespace PixiEditor.Models.Controllers
             Dictionary<Coordinates, Color> values = new Dictionary<Coordinates, Color>();
             using (Manager.ActiveLayer.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
             {
-                var relativeCoords = Manager.ActiveLayer.ConvertToRelativeCoordinates(coordinates);
+                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));
                 }
-
             }
+
             return new BitmapPixelChanges(values);
         }
 
         private void UseToolOnPreviewLayer(List<Coordinates> mouseMove)
         {
             LayerChange[] modifiedLayers;
-            if (mouseMove.Count > 0 && mouseMove[0] != _lastMousePos)
+            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;
+                lastModifiedLayers = modifiedLayers;
             }
         }
     }
-}
-
-public class BitmapChangedEventArgs : EventArgs
-{
-    public BitmapPixelChanges PixelsChanged { get; set; }
-    public BitmapPixelChanges OldPixelsValues { get; set; }
-    public int ChangedLayerIndex { get; set; }
-
-    public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, BitmapPixelChanges oldPixelsValues,
-        int changedLayerIndex)
-    {
-        PixelsChanged = pixelsChanged;
-        OldPixelsValues = oldPixelsValues;
-        ChangedLayerIndex = changedLayerIndex;
-    }
 }

+ 106 - 98
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -1,99 +1,107 @@
-using System.IO;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.DataHolders;
-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 (var pngStream = new MemoryStream())
-            {
-                DataObject data = new DataObject();
-                var 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;
-            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());
-                    }
-                }
-            else if (dao.GetDataPresent(DataFormats.Dib))
-                finalImage = new WriteableBitmap(Clipboard.GetImage()!);
-            else if (dao.GetDataPresent(DataFormats.Bitmap))
-                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource)!);
-
-            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);
-        }
-    }
+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;
+            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());
+                    }
+                }
+            }
+            else if (dao.GetDataPresent(DataFormats.Dib))
+            {
+                finalImage = new WriteableBitmap(Clipboard.GetImage() !);
+            }
+            else if (dao.GetDataPresent(DataFormats.Bitmap))
+            {
+                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource) !);
+            }
+
+            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);
+        }
+
+        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; }
+    }
+}

+ 32 - 16
PixiEditor/Models/Controllers/MouseMovementController.cs

@@ -1,19 +1,28 @@
 using System;
 using System.Collections.Generic;
-using Accessibility;
+using System.Windows.Input;
 using PixiEditor.Models.Position;
 
 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<MouseEventArgs> OnMouseDown;
+
+        public event EventHandler<MouseEventArgs> OnMouseUp;
+
         public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
+
         public event EventHandler StoppedRecordingChanges;
 
+        public List<Coordinates> LastMouseMoveCoordinates { get; set; } = new List<Coordinates>();
+
+        public bool IsRecordingChanges { get; private set; }
+
+        public bool ClickedOnCanvas { get; set; }
+
         public void StartRecordingMouseMovementChanges(bool clickedOnCanvas)
         {
             if (IsRecordingChanges == false)
@@ -28,22 +37,39 @@ namespace PixiEditor.Models.Controllers
         public void RecordMouseMovementChange(Coordinates mouseCoordinates)
         {
             if (IsRecordingChanges)
+            {
                 if (LastMouseMoveCoordinates.Count == 0 || mouseCoordinates != LastMouseMoveCoordinates[^1])
                 {
                     LastMouseMoveCoordinates.Add(mouseCoordinates);
                     MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
                 }
+            }
         }
 
         /// <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));
         }
 
+        /// <summary>
+        /// Plain mouse down, does not affect mouse recordings.
+        /// </summary>
+        public void MouseDown(MouseEventArgs args)
+        {
+            OnMouseDown?.Invoke(this, args);
+        }
+
+        /// <summary>
+        /// Plain mouse up, does not affect mouse recordings.
+        /// </summary>
+        public void MouseUp(MouseEventArgs args)
+        {
+            OnMouseUp?.Invoke(this, args);
+        }
+
         public void StopRecordingMouseMovementChanges()
         {
             if (IsRecordingChanges)
@@ -54,14 +80,4 @@ namespace PixiEditor.Models.Controllers
             }
         }
     }
-}
-
-public class MouseMovementEventArgs : EventArgs
-{
-    public Coordinates NewPosition { get; set; }
-
-    public MouseMovementEventArgs(Coordinates mousePosition)
-    {
-        NewPosition = mousePosition;
-    }
 }

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

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

+ 51 - 37
PixiEditor/Models/Controllers/PixelChangesController.cs

@@ -1,31 +1,29 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Windows.Media;
 using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
 
 namespace PixiEditor.Models.Controllers
 {
     public class PixelChangesController
     {
         private Dictionary<int, LayerChange> LastChanges { get; set; }
+
         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)
             {
                 if (LastChanges == null)
                 {
-                    LastChanges = new Dictionary<int, LayerChange> {{changes.LayerIndex, changes}};
-                    LastOldValues = new Dictionary<int, LayerChange> {{oldValues.LayerIndex, oldValues}};
+                    LastChanges = new Dictionary<int, LayerChange> { { changes.LayerIndex, changes } };
+                    LastOldValues = new Dictionary<int, LayerChange> { { oldValues.LayerIndex, oldValues } };
                 }
                 else if (LastChanges.ContainsKey(changes.LayerIndex))
                 {
@@ -38,46 +36,29 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
-        private void AddNewLayerChange(LayerChange changes, LayerChange oldValues)
-        {
-            LastChanges[changes.LayerIndex] = changes;
-            LastOldValues[changes.LayerIndex] = oldValues;
-        }
-
-        private void AddToExistingLayerChange(LayerChange layerChange, LayerChange oldValues)
-        {
-            foreach (var change in layerChange.PixelChanges.ChangedPixels)
-                if (LastChanges[layerChange.LayerIndex].PixelChanges.ChangedPixels.ContainsKey(change.Key))
-                    continue;
-                else
-                    LastChanges[layerChange.LayerIndex].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
-
-            foreach (var change in oldValues.PixelChanges.ChangedPixels)
-                if (LastOldValues[layerChange.LayerIndex].PixelChanges.ChangedPixels.ContainsKey(change.Key))
-                    continue;
-                else
-                    LastOldValues[layerChange.LayerIndex].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
-        }
-
         /// <summary>
         ///     Returns all changes and deletes them from controller.
         /// </summary>
-        /// <returns>Tuple array with new changes and old values</returns>
+        /// <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;
+            // 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 (var change in LastChanges)
+            foreach (KeyValuePair<int, LayerChange> change in LastChanges)
             {
-                Dictionary<Coordinates, Color> pixelChanges =
+                Dictionary<Position.Coordinates, System.Windows.Media.Color> pixelChanges =
                     change.Value.PixelChanges.ChangedPixels.ToDictionary(entry => entry.Key, entry => entry.Value);
-                Dictionary<Coordinates, Color> oldValues = LastOldValues[change.Key].PixelChanges.ChangedPixels
+                Dictionary<Position.Coordinates, System.Windows.Media.Color> oldValues = LastOldValues[change.Key].PixelChanges.ChangedPixels
                     .ToDictionary(entry => entry.Key, entry => entry.Value);
 
-                var tmp = new LayerChange(new BitmapPixelChanges(pixelChanges), change.Key);
-                var oldValuesTmp = new LayerChange(new BitmapPixelChanges(oldValues), change.Key);
+                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++;
@@ -87,5 +68,38 @@ namespace PixiEditor.Models.Controllers
             LastOldValues = null;
             return result;
         }
+
+        private void AddNewLayerChange(LayerChange changes, LayerChange oldValues)
+        {
+            LastChanges[changes.LayerIndex] = changes;
+            LastOldValues[changes.LayerIndex] = oldValues;
+        }
+
+        private void AddToExistingLayerChange(LayerChange layerChange, LayerChange oldValues)
+        {
+            foreach (KeyValuePair<Position.Coordinates, System.Windows.Media.Color> change in layerChange.PixelChanges.ChangedPixels)
+            {
+                if (LastChanges[layerChange.LayerIndex].PixelChanges.ChangedPixels.ContainsKey(change.Key))
+                {
+                    continue;
+                }
+                else
+                {
+                    LastChanges[layerChange.LayerIndex].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
+                }
+            }
+
+            foreach (KeyValuePair<Position.Coordinates, System.Windows.Media.Color> change in oldValues.PixelChanges.ChangedPixels)
+            {
+                if (LastOldValues[layerChange.LayerIndex].PixelChanges.ChangedPixels.ContainsKey(change.Key))
+                {
+                    continue;
+                }
+                else
+                {
+                    LastOldValues[layerChange.LayerIndex].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
+                }
+            }
+        }
     }
 }

+ 13 - 8
PixiEditor/Models/Controllers/Shortcuts/Shortcut.cs

@@ -4,13 +4,7 @@ namespace PixiEditor.Models.Controllers.Shortcuts
 {
     public class Shortcut
     {
-        public Key ShortcutKey { get; set; }
-        public ModifierKeys Modifier { get; set; }
-        public ICommand Command { get; set; }
-        public object CommandParameter { get; set; }
-
-        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;
@@ -18,9 +12,20 @@ namespace PixiEditor.Models.Controllers.Shortcuts
             CommandParameter = commandParameter;
         }
 
+        public Key ShortcutKey { get; set; }
+
+        public ModifierKeys Modifier { get; set; }
+
+        public ICommand Command { get; set; }
+
+        public object CommandParameter { get; set; }
+
         public void Execute()
         {
-            if (Command.CanExecute(CommandParameter)) Command.Execute(CommandParameter);
+            if (Command.CanExecute(CommandParameter))
+            {
+                Command.Execute(CommandParameter);
+            }
         }
     }
 }

+ 13 - 6
PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs

@@ -6,30 +6,37 @@ namespace PixiEditor.Models.Controllers.Shortcuts
 {
     public class ShortcutController
     {
-        public static bool BlockShortcutExecution { get; set; }
-
-        public List<Shortcut> Shortcuts { get; set; }
-        public Shortcut LastShortcut { get; private set; }
-
         public ShortcutController()
         {
             Shortcuts = new List<Shortcut>();
         }
 
+        public static bool BlockShortcutExecution { get; set; }
+
+        public List<Shortcut> Shortcuts { get; set; }
+
+        public Shortcut LastShortcut { get; private set; }
+
         public void KeyPressed(Key key, ModifierKeys modifiers)
         {
             if (!BlockShortcutExecution)
             {
                 Shortcut[] shortcuts = Shortcuts.FindAll(x => x.ShortcutKey == key).ToArray();
-                if (shortcuts.Length < 1) return;
+                if (shortcuts.Length < 1)
+                {
+                    return;
+                }
+
                 shortcuts = shortcuts.OrderByDescending(x => x.Modifier).ToArray();
                 for (int i = 0; i < shortcuts.Length; i++)
+                {
                     if (modifiers.HasFlag(shortcuts[i].Modifier))
                     {
                         shortcuts[i].Execute();
                         LastShortcut = shortcuts[i];
                         break;
                     }
+                }
             }
         }
     }

+ 96 - 84
PixiEditor/Models/Controllers/UndoManager.cs

@@ -1,85 +1,97 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using PixiEditor.Models.DataHolders;
-
-namespace PixiEditor.Models.Controllers
-{
-    public static class UndoManager
-    {
-        private static bool _lastChangeWasUndo;
-        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>
-        /// <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
-                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();
-            if (change.ReverseProcess == null)
-                SetPropertyValue(change.Root, change.Property, change.OldValue);
-            else
-                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();
-            if (change.Process == null)
-                SetPropertyValue(change.Root, change.Property, change.NewValue);
-            else
-                change.Process(change.ProcessArguments);
-            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++)
-            {
-                PropertyInfo propertyToGet = target.GetType().GetProperty(bits[i]);
-                target = propertyToGet.GetValue(target, null);
-            }
-
-            PropertyInfo propertyToSet = target.GetType().GetProperty(bits.Last());
-            propertyToSet.SetValue(target, value, null);
-        }
-    }
+using System.Collections.Generic;
+using System.Linq;
+using PixiEditor.Models.DataHolders;
+
+namespace PixiEditor.Models.Controllers
+{
+    public static class UndoManager
+    {
+        private static bool lastChangeWasUndo;
+
+        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();
+            if (change.ReverseProcess == null)
+            {
+                SetPropertyValue(change.Root, change.Property, change.OldValue);
+            }
+            else
+            {
+                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();
+            if (change.Process == null)
+            {
+                SetPropertyValue(change.Root, change.Property, change.NewValue);
+            }
+            else
+            {
+                change.Process(change.ProcessArguments);
+            }
+
+            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);
+            }
+
+            System.Reflection.PropertyInfo propertyToSet = target.GetType().GetProperty(bits.Last());
+            propertyToSet.SetValue(target, value, null);
+        }
+    }
 }

+ 82 - 70
PixiEditor/Models/DataHolders/BitmapPixelChanges.cs

@@ -1,71 +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>());
-        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>();
-            foreach (var 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)
-        {
-            if (changes == null || changes.Length == 0) throw new ArgumentException();
-            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)
-        {
-            var coordinateArray = coordinates.ToArray();
-            var colorArray = color.ToArray();
-            if (coordinateArray.Length != colorArray.Length) throw new ArrayLengthMismatchException();
-            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);
-        }
-    }
+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 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)
+        {
+            if (changes == null || changes.Length == 0)
+            {
+                throw new ArgumentException();
+            }
+
+            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>
+        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>();
+            for (int i = 0; i < coordinateArray.Length; i++)
+            {
+                dict.Add(coordinateArray[i], colorArray[i]);
+            }
+
+            return new BitmapPixelChanges(dict);
+        }
+    }
 }

+ 97 - 77
PixiEditor/Models/DataHolders/Change.cs

@@ -1,78 +1,98 @@
-using System;
-
-namespace PixiEditor.Models.DataHolders
-{
-    [Serializable]
-    public class Change
-    {
-        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[] ProcessArguments;
-        public object[] ReverseProcessArguments;
-        public object Root { get; set; }
-
-        /// <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;
-        }
-    }
+using System;
+
+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;
+        }
+
+        /// <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; }
+    }
 }

+ 327 - 304
PixiEditor/Models/DataHolders/Document.cs

@@ -1,305 +1,328 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Helpers;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.Tools;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Models.DataHolders
-{
-    public class Document : NotifyableObject
-    {
-        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>();
-        private int _activeLayerIndex;
-        private int _height;
-        private int _width;
-
-        public Document(int width, int height)
-        {
-            Width = width;
-            Height = height;
-        }
-
-        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)
-        {
-            if (anchor.HasFlag(AnchorPoint.Center))
-                return Math.Abs(destWidth / 2 - srcWidth / 2);
-            if (anchor.HasFlag(AnchorPoint.Right)) return Math.Abs(destWidth - srcWidth);
-            return 0;
-        }
-
-        private int GetOffsetYForAnchor(int srcHeight, int destHeight, AnchorPoint anchor)
-        {
-            if (anchor.HasFlag(AnchorPoint.Middle))
-                return Math.Abs(destHeight / 2 - srcHeight / 2);
-            if (anchor.HasFlag(AnchorPoint.Bottom)) 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;
-
-            int width = biggestX - smallestX;
-            int height = biggestY - smallestY;
-            var 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()
-        {
-            var 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;
-                if (Layers[i].OffsetX + Layers[i].Width > biggestX)
-                {
-                    biggestX = Layers[i].OffsetX + Layers[i].Width;
-                }
-
-                if (Layers[i].OffsetY < smallestY)
-                    smallestY = Layers[i].OffsetY;
-                if (Layers[i].OffsetY + Layers[i].Height > biggestY)
-                {
-                    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++)
-            {
-                var 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;
-
-            var contentCenter = CoordinatesCalculator.GetCenterPoint(points.Coords1, points.Coords2);
-            var 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 int OldWidth { get; set; }
-        public int OldHeight { get; set; }
-        public int NewWidth { get; set; }
-        public int NewHeight { get; set; }
-
-        public DocumentSizeChangedEventArgs(int oldWidth, int oldHeight, int newWidth, int newHeight)
-        {
-            OldWidth = oldWidth;
-            OldHeight = oldHeight;
-            NewWidth = newWidth;
-            NewHeight = newHeight;
-        }
-    }
+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));
+            }
+
+            if (anchor.HasFlag(AnchorPoint.Right))
+            {
+                return Math.Abs(destWidth - srcWidth);
+            }
+
+            return 0;
+        }
+
+        private int GetOffsetYForAnchor(int srcHeight, int destHeight, AnchorPoint anchor)
+        {
+            if (anchor.HasFlag(AnchorPoint.Middle))
+            {
+                return Math.Abs((destHeight / 2) - (srcHeight / 2));
+            }
+
+            if (anchor.HasFlag(AnchorPoint.Bottom))
+            {
+                return Math.Abs(destHeight - srcHeight);
+            }
+
+            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++)
+            {
+                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;
+        }
+
+        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;
+                }
+
+                if (Layers[i].OffsetX + Layers[i].Width > biggestX)
+                {
+                    biggestX = Layers[i].OffsetX + Layers[i].Width;
+                }
+
+                if (Layers[i].OffsetY < smallestY)
+                {
+                    smallestY = Layers[i].OffsetY;
+                }
+
+                if (Layers[i].OffsetY + Layers[i].Height > biggestY)
+                {
+                    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>
+        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);
+        }
+    }
 }

+ 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; }
+    }
+}

+ 4 - 4
PixiEditor/Models/DataHolders/LayerChange.cs

@@ -1,14 +1,10 @@
 using PixiEditor.Models.Layers;
-using PixiEditor.Models.Tools;
 using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.DataHolders
 {
     public class LayerChange
     {
-        public BitmapPixelChanges PixelChanges { get; set; }
-        public int LayerIndex { get; set; }
-
         public LayerChange(BitmapPixelChanges pixelChanges, int layerIndex)
         {
             PixelChanges = pixelChanges;
@@ -20,5 +16,9 @@ namespace PixiEditor.Models.DataHolders
             PixelChanges = pixelChanges;
             LayerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.IndexOf(layer);
         }
+
+        public BitmapPixelChanges PixelChanges { get; set; }
+
+        public int LayerIndex { get; set; }
     }
 }

+ 13 - 15
PixiEditor/Models/DataHolders/Selection.cs

@@ -6,38 +6,36 @@ using PixiEditor.Helpers;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
-using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.DataHolders
 {
     public class Selection : NotifyableObject
     {
+        private readonly Color selectionBlue;
+        private Layer selectionLayer;
+
+        public Selection(Coordinates[] selectedPoints)
+        {
+            SelectedPoints = new ObservableCollection<Coordinates>(selectedPoints);
+            SelectionLayer = new Layer("_selectionLayer");
+            selectionBlue = Color.FromArgb(127, 142, 202, 255);
+        }
+
         public ObservableCollection<Coordinates> SelectedPoints { get; private set; }
 
         public Layer SelectionLayer
         {
-            get => _selectionLayer;
+            get => selectionLayer;
             set
             {
-                _selectionLayer = value;
+                selectionLayer = value;
                 RaisePropertyChanged("SelectionLayer");
             }
         }
 
-        private readonly Color _selectionBlue;
-        private Layer _selectionLayer;
-
-        public Selection(Coordinates[] selectedPoints)
-        {
-            SelectedPoints = new ObservableCollection<Coordinates>(selectedPoints);
-            SelectionLayer = new Layer("_selectionLayer");
-            _selectionBlue = Color.FromArgb(127, 142, 202, 255);
-        }
-
         public void SetSelection(IEnumerable<Coordinates> selection, SelectionType mode)
         {
-            Color selectionColor = _selectionBlue;
+            Color selectionColor = selectionBlue;
             switch (mode)
             {
                 case SelectionType.New:

+ 9 - 6
PixiEditor/Models/DataHolders/SerializableDocument.cs

@@ -11,11 +11,6 @@ namespace PixiEditor.Models.DataHolders
     [Serializable]
     public class SerializableDocument
     {
-        public int Width { get; set; }
-        public int Height { get; set; }
-        public SerializableLayer[] Layers { get; set; }
-        public Tuple<byte, byte, byte, byte>[] Swatches { get; set; }
-
         public SerializableDocument(Document document)
         {
             Width = document.Width;
@@ -24,6 +19,14 @@ namespace PixiEditor.Models.DataHolders
             Swatches = document.Swatches.Select(x => new Tuple<byte, byte, byte, byte>(x.A, x.R, x.G, x.B)).ToArray();
         }
 
+        public int Width { get; set; }
+
+        public int Height { get; set; }
+
+        public SerializableLayer[] Layers { get; set; }
+
+        public Tuple<byte, byte, byte, byte>[] Swatches { get; set; }
+
         public Document ToDocument()
         {
             Document document = new Document(Width, Height)
@@ -42,7 +45,7 @@ namespace PixiEditor.Models.DataHolders
             {
                 SerializableLayer serLayer = Layers[i];
                 Layer layer =
-                    new Layer(serLayer.Name,BitmapUtils.BytesToWriteableBitmap(serLayer.Width, serLayer.Height, serLayer.BitmapBytes))
+                    new Layer(serLayer.Name, BitmapUtils.BytesToWriteableBitmap(serLayer.Width, serLayer.Height, serLayer.BitmapBytes))
                     {
                         IsVisible = serLayer.IsVisible,
                         Offset = new Thickness(serLayer.OffsetX, serLayer.OffsetY, 0, 0),

+ 5 - 1
PixiEditor/Models/Dialogs/ConfirmationDialog.cs

@@ -11,7 +11,11 @@ namespace PixiEditor.Models.Dialogs
             {
                 Body = message
             };
-            if ((bool) popup.ShowDialog()) return popup.Result ? ConfirmationType.Yes : ConfirmationType.No;
+            if ((bool)popup.ShowDialog())
+            {
+                return popup.Result ? ConfirmationType.Yes : ConfirmationType.No;
+            }
+
             return ConfirmationType.Canceled;
         }
     }

+ 22 - 23
PixiEditor/Models/Dialogs/ExportFileDialog.cs

@@ -5,14 +5,26 @@ namespace PixiEditor.Models.Dialogs
 {
     public class ExportFileDialog : CustomDialog
     {
+        private int fileHeight;
+
+        private string filePath;
+
+        private int fileWidth;
+
+        public ExportFileDialog(Size fileDimensions)
+        {
+            FileHeight = (int)fileDimensions.Height;
+            FileWidth = (int)fileDimensions.Width;
+        }
+
         public int FileWidth
         {
-            get => _fileWidth;
+            get => fileWidth;
             set
             {
-                if (_fileWidth != value)
+                if (fileWidth != value)
                 {
-                    _fileWidth = value;
+                    fileWidth = value;
                     RaisePropertyChanged("Width");
                 }
             }
@@ -20,12 +32,12 @@ namespace PixiEditor.Models.Dialogs
 
         public int FileHeight
         {
-            get => _fileHeight;
+            get => fileHeight;
             set
             {
-                if (_fileHeight != value)
+                if (fileHeight != value)
                 {
-                    _fileHeight = value;
+                    fileHeight = value;
                     RaisePropertyChanged("FileHeight");
                 }
             }
@@ -33,30 +45,17 @@ namespace PixiEditor.Models.Dialogs
 
         public string FilePath
         {
-            get => _filePath;
+            get => filePath;
             set
             {
-                if (_filePath != value)
+                if (filePath != value)
                 {
-                    _filePath = value;
+                    filePath = value;
                     RaisePropertyChanged("FilePath");
                 }
             }
         }
 
-        private int _fileHeight;
-
-
-        private string _filePath;
-
-        private int _fileWidth;
-
-        public ExportFileDialog(Size fileDimensions)
-        {
-            FileHeight = (int) fileDimensions.Height;
-            FileWidth = (int) fileDimensions.Width;
-        }
-
         public override bool ShowDialog()
         {
             SaveFilePopup popup = new SaveFilePopup
@@ -72,7 +71,7 @@ namespace PixiEditor.Models.Dialogs
                 FilePath = popup.SavePath;
             }
 
-            return (bool) popup.DialogResult;
+            return (bool)popup.DialogResult;
         }
     }
 }

+ 19 - 18
PixiEditor/Models/Dialogs/ImportFileDialog.cs

@@ -4,14 +4,19 @@ namespace PixiEditor.Models.Dialogs
 {
     internal class ImportFileDialog : CustomDialog
     {
+        private int fileHeight;
+
+        private string filePath;
+        private int fileWidth;
+
         public int FileWidth
         {
-            get => _fileWidth;
+            get => fileWidth;
             set
             {
-                if (_fileWidth != value)
+                if (fileWidth != value)
                 {
-                    _fileWidth = value;
+                    fileWidth = value;
                     RaisePropertyChanged("Width");
                 }
             }
@@ -19,12 +24,12 @@ namespace PixiEditor.Models.Dialogs
 
         public int FileHeight
         {
-            get => _fileHeight;
+            get => fileHeight;
             set
             {
-                if (_fileHeight != value)
+                if (fileHeight != value)
                 {
-                    _fileHeight = value;
+                    fileHeight = value;
                     RaisePropertyChanged("FileHeight");
                 }
             }
@@ -32,27 +37,23 @@ namespace PixiEditor.Models.Dialogs
 
         public string FilePath
         {
-            get => _filePath;
+            get => filePath;
             set
             {
-                if (_filePath != value)
+                if (filePath != value)
                 {
-                    _filePath = value;
+                    filePath = value;
                     RaisePropertyChanged("FilePath");
                 }
             }
         }
 
-        private int _fileHeight;
-
-
-        private string _filePath;
-        private int _fileWidth;
-
         public override bool ShowDialog()
         {
-            ImportFilePopup popup = new ImportFilePopup();
-            popup.FilePath = FilePath;
+            ImportFilePopup popup = new ImportFilePopup
+            {
+                FilePath = FilePath
+            };
             popup.ShowDialog();
             if (popup.DialogResult == true)
             {
@@ -61,7 +62,7 @@ namespace PixiEditor.Models.Dialogs
                 FilePath = popup.FilePath;
             }
 
-            return (bool) popup.DialogResult;
+            return (bool)popup.DialogResult;
         }
     }
 }

+ 11 - 11
PixiEditor/Models/Dialogs/NewFileDialog.cs

@@ -5,14 +5,18 @@ namespace PixiEditor.Models.Dialogs
 {
     public class NewFileDialog : CustomDialog
     {
+        private int height;
+
+        private int width;
+
         public int Width
         {
-            get => _width;
+            get => width;
             set
             {
-                if (_width != value)
+                if (width != value)
                 {
-                    _width = value;
+                    width = value;
                     RaisePropertyChanged("Width");
                 }
             }
@@ -20,28 +24,24 @@ namespace PixiEditor.Models.Dialogs
 
         public int Height
         {
-            get => _height;
+            get => height;
             set
             {
-                if (_height != value)
+                if (height != value)
                 {
-                    _height = value;
+                    height = value;
                     RaisePropertyChanged("Height");
                 }
             }
         }
 
-        private int _height;
-
-        private int _width;
-
         public override bool ShowDialog()
         {
             Window popup = new NewFilePopup();
             popup.ShowDialog();
             Height = (popup as NewFilePopup).FileHeight;
             Width = (popup as NewFilePopup).FileWidth;
-            return (bool) popup.DialogResult;
+            return (bool)popup.DialogResult;
         }
     }
 }

+ 19 - 18
PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs

@@ -5,17 +5,28 @@ namespace PixiEditor.Models.Dialogs
 {
     public class ResizeDocumentDialog : CustomDialog
     {
+        private int height;
+        private int width;
+
+        public ResizeDocumentDialog(int currentWidth, int currentHeight, bool openResizeCanvas = false)
+        {
+            Width = currentWidth;
+            Height = currentHeight;
+            OpenResizeCanvas = openResizeCanvas;
+        }
+
         public bool OpenResizeCanvas { get; set; }
+
         public AnchorPoint ResizeAnchor { get; set; }
 
         public int Width
         {
-            get => _width;
+            get => width;
             set
             {
-                if (_width != value)
+                if (width != value)
                 {
-                    _width = value;
+                    width = value;
                     RaisePropertyChanged("Width");
                 }
             }
@@ -23,27 +34,17 @@ namespace PixiEditor.Models.Dialogs
 
         public int Height
         {
-            get => _height;
+            get => height;
             set
             {
-                if (_height != value)
+                if (height != value)
                 {
-                    _height = value;
+                    height = value;
                     RaisePropertyChanged("Height");
                 }
             }
         }
 
-        private int _height;
-        private int _width;
-
-        public ResizeDocumentDialog(int currentWidth, int currentHeight, bool openResizeCanvas = false)
-        {
-            Width = currentWidth;
-            Height = currentHeight;
-            OpenResizeCanvas = openResizeCanvas;
-        }
-
         public override bool ShowDialog()
         {
             return OpenResizeCanvas ? ShowResizeCanvasDialog() : ShowResizeDocumentCanvas();
@@ -64,7 +65,7 @@ namespace PixiEditor.Models.Dialogs
                 Height = popup.NewHeight;
             }
 
-            return (bool) popup.DialogResult;
+            return (bool)popup.DialogResult;
         }
 
         private bool ShowResizeCanvasDialog()
@@ -83,7 +84,7 @@ namespace PixiEditor.Models.Dialogs
                 ResizeAnchor = popup.SelectedAnchorPoint;
             }
 
-            return (bool) popup.DialogResult;
+            return (bool)popup.DialogResult;
         }
     }
 }

+ 4 - 7
PixiEditor/Models/Enums/BrightnessMode.cs

@@ -1,11 +1,8 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace PixiEditor.Models.Enums
+namespace PixiEditor.Models.Enums
 {
     public enum BrightnessMode
     {
-        Default, Repeat
+        Default,
+        Repeat
     }
-}
+}

+ 4 - 7
PixiEditor/Models/Enums/CapType.cs

@@ -1,11 +1,8 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace PixiEditor.Models.Enums
+namespace PixiEditor.Models.Enums
 {
     public enum CapType
     {
-        Square, Round
+        Square,
+        Round
     }
-}
+}

+ 1 - 1
PixiEditor/Models/Enums/FileType.cs

@@ -2,6 +2,6 @@
 {
     public enum FileType
     {
-        PNG = 0
+        Png = 0
     }
 }

+ 2 - 2
PixiEditor/Models/Events/DocumentChangedEventArgs.cs

@@ -4,11 +4,11 @@ namespace PixiEditor.Models.Events
 {
     public class DocumentChangedEventArgs
     {
-        public Document NewDocument { get; set; }
-
         public DocumentChangedEventArgs(Document newDocument)
         {
             NewDocument = newDocument;
         }
+
+        public Document NewDocument { get; set; }
     }
 }

+ 1 - 1
PixiEditor/Models/IO/BinarySerialization.cs

@@ -19,7 +19,7 @@ namespace PixiEditor.Models.IO
             using (Stream stream = File.Open(path, FileMode.Open))
             {
                 BinaryFormatter formatter = new BinaryFormatter();
-                return (T) formatter.Deserialize(stream);
+                return (T)formatter.Deserialize(stream);
             }
         }
     }

+ 96 - 95
PixiEditor/Models/IO/Exporter.cs

@@ -1,96 +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));
-
-            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);
-            }
-        }
-    }
+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);
+            }
+        }
+    }
 }

+ 8 - 8
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>
@@ -16,17 +16,17 @@ namespace PixiEditor.Models.IO
         /// <returns></returns>
         public static WriteableBitmap ImportImage(string path, int width, int height)
         {
-            var wbmp = ImportImage(path);
-            if (wbmp.PixelWidth != width || wbmp.PixelHeight != height)
-            {
-                return wbmp.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
-            }
-
+            WriteableBitmap wbmp = ImportImage(path);
+            if (wbmp.PixelWidth != width || wbmp.PixelHeight != height)
+            {
+                return wbmp.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+            }
+
             return wbmp;
         }
 
         /// <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)

+ 29 - 25
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Windows;
-using System.Windows.Interop;
+using System.Collections.Generic;
 using System.Windows.Media.Imaging;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
@@ -13,14 +9,13 @@ namespace PixiEditor.Models.ImageManipulation
     public static class BitmapUtils
     {
         /// <summary>
-        ///     Converts pixel bytes to WriteableBitmap
+        ///     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)
+        /// <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);
@@ -30,18 +25,20 @@ namespace PixiEditor.Models.ImageManipulation
         /// <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>
+        /// <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++)
+                for (int i = 0; i < layers.Length; i++)
+                {
+                    for (int y = 0; y < finalBitmap.Height; y++)
+                    {
                         for (int x = 0; x < finalBitmap.Width; x++)
                         {
                             Color color = layers[i].GetPixelWithOffset(x, y);
@@ -58,8 +55,14 @@ namespace PixiEditor.Models.ImageManipulation
                             {
                                 color = Color.FromArgb((byte)(color.A * layers[i].Opacity), color.R, color.G, color.B);
                             }
-                            if (color.A != 0 || color.R != 0 || color.B != 0 || color.G != 0) finalBitmap.SetPixel(x, y, color);
-                        }
+
+                            if (color.A != 0 || color.R != 0 || color.B != 0 || color.G != 0)
+                            {
+                                finalBitmap.SetPixel(x, y, color);
+                            }
+                        }
+                    }
+                }
             }
 
             return finalBitmap;
@@ -75,18 +78,19 @@ namespace PixiEditor.Models.ImageManipulation
 
                 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;
+                            position.Y > layers[i].Height - 1)
+                        {
+                            continue;
+                        }
+
                         pixels[j] = layers[i].GetPixel(position.X, position.Y);
                     }
                 }
-
-
+
                 result[layers[i]] = pixels;
             }
 

+ 98 - 77
PixiEditor/Models/ImageManipulation/Morphology.cs

@@ -1,78 +1,99 @@
-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++)
-            {
-                byte value = 0;
-
-                //Apply dilation
-                for (int ykernel = -kernelOffset; ykernel <= kernelOffset; ykernel++)
-                for (int xkernel = -kernelOffset; xkernel <= kernelOffset; xkernel++)
-                    if (mask[xkernel + kernelOffset, ykernel + kernelOffset] == 1)
-                        value = Math.Max(value, byteImg[x + xkernel, y + ykernel]);
-                    else
-                        continue;
-                //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);
-
-            for (int y = 0; y < byteArray.GetLength(1); y++)
-            for (int x = 0; x < width; x++)
-                if (byteArray[x, y] == 1)
-                    output.Add(new Coordinates(x + offset.X, y + offset.Y));
-            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++)
-            {
-                Coordinates cords = new Coordinates(x + minX, y + minY);
-                array[x + margin, y + margin] = points.Contains(cords) ? (byte) 1 : (byte) 0;
-            }
-            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);
-        }
-    }
+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++)
+                {
+                    byte value = 0;
+
+                    // Apply dilation
+                    for (int ykernel = -kernelOffset; ykernel <= kernelOffset; ykernel++)
+                    {
+                        for (int xkernel = -kernelOffset; xkernel <= kernelOffset; xkernel++)
+                        {
+                            if (mask[xkernel + kernelOffset, ykernel + kernelOffset] == 1)
+                            {
+                                value = Math.Max(value, byteImg[x + xkernel, y + ykernel]);
+                            }
+                            else
+                            {
+                                continue;
+                            }
+                        }
+                    }
+
+                    // 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);
+
+            for (int y = 0; y < byteArray.GetLength(1); y++)
+            {
+                for (int x = 0; x < width; x++)
+                {
+                    if (byteArray[x, y] == 1)
+                    {
+                        output.Add(new Coordinates(x + offset.X, y + offset.Y));
+                    }
+                }
+            }
+
+            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++)
+                {
+                    Coordinates cords = new Coordinates(x + minX, y + minY);
+                    array[x + margin, y + margin] = points.Contains(cords) ? (byte)1 : (byte)0;
+                }
+            }
+
+            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);
+        }
+    }
 }

+ 8 - 5
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;
@@ -20,8 +20,11 @@ namespace PixiEditor.Models.ImageManipulation
         public static Coordinates[] Translate(Coordinates[] points, Coordinates vector)
         {
             Coordinates[] translatedPoints = new Coordinates[points.Length];
-            for (int i = 0; i < translatedPoints.Length; i++)
-                translatedPoints[i] = new Coordinates(points[i].X + vector.X, points[i].Y + vector.Y);
+            for (int i = 0; i < translatedPoints.Length; i++)
+            {
+                translatedPoints[i] = new Coordinates(points[i].X + vector.X, points[i].Y + vector.Y);
+            }
+
             return translatedPoints;
         }
     }

+ 8 - 8
PixiEditor/Models/Layers/BasicLayer.cs

@@ -6,28 +6,28 @@ namespace PixiEditor.Models.Layers
     [Serializable]
     public class BasicLayer : NotifyableObject
     {
+        private int height;
+
+        private int width;
+
         public int Width
         {
-            get => _width;
+            get => width;
             set
             {
-                _width = value;
+                width = value;
                 RaisePropertyChanged("Width");
             }
         }
 
         public int Height
         {
-            get => _height;
+            get => height;
             set
             {
-                _height = value;
+                height = value;
                 RaisePropertyChanged("Height");
             }
         }
-
-        private int _height;
-
-        private int _width;
     }
 }

+ 492 - 462
PixiEditor/Models/Layers/Layer.cs

@@ -1,463 +1,493 @@
-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;
-using PixiEditor.Models.Tools;
-
-namespace PixiEditor.Models.Layers
-{
-    public class Layer : BasicLayer
-    {
-
-        private const int SizeOfArgb = 4;
-
-        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");
-            }
-        }
-
-        private float _opacity = 1;
-
-        public float Opacity
-        {
-            get => _opacity;
-            set
-            {
-                _opacity = value;
-                RaisePropertyChanged("Opacity");
-            }
-        }
-
-        public int OffsetX => (int) Offset.Left;
-
-        public int OffsetY => (int) Offset.Top;
-
-        private Thickness _offset;
-
-        public Thickness Offset
-        {
-            get => _offset;
-            set
-            {
-                _offset = value;
-                RaisePropertyChanged("Offset");
-            }
-        }
-
-        private bool _isActive;
-
-        private bool _isRenaming;
-        private bool _isVisible = true;
-        private WriteableBitmap _layerBitmap;
-
-        private string _name;
-        private bool _clipRequested;
-
-        public int MaxWidth { get; set; } = int.MaxValue;
-        public int MaxHeight { get; set; } = int.MaxValue;
-
-        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;
-        }
-
-        /// <summary>
-        /// Returns clone of layer
-        /// </summary>
-        /// <returns></returns>
-        public Layer Clone()
-        {
-            return new Layer(Name, LayerBitmap.Clone())
-            {
-                IsVisible = this.IsVisible,
-                Offset = this.Offset,
-                MaxHeight = this.MaxHeight,
-                MaxWidth = this.MaxWidth,
-                Opacity = this.Opacity,
-                IsActive = this.IsActive,
-                IsRenaming = this.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)
-        {
-            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)
-        {
-            if (pixels.ChangedPixels == null || pixels.ChangedPixels.Count == 0) return;
-            if(dynamicResize)
-                DynamicResize(pixels);
-            if(applyOffset)
-                pixels.ChangedPixels = GetRelativePosition(pixels.ChangedPixels);
-            LastRelativeCoordinates = pixels.ChangedPixels;
-
-            using (var ctx = LayerBitmap.GetBitmapContext())
-            {
-                foreach (var 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];
-            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)
-        {
-            if (pixels.ChangedPixels.Count == 0) return;
-            ResetOffset(pixels);
-            var 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);
-                }
-                if (newMinX < 0 || newMinY < 0)
-                {
-                    IncreaseSizeToTop(newMinX, newMinY);
-                }
-            }
-
-            if(borderData.Item2) //if clip is requested
-            {
-                _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 (var pixel in pixels.ChangedPixels)
-            {
-                if (pixel.Key.X < minX) minX = pixel.Key.X;
-                else if (pixel.Key.X > maxX) maxX = pixel.Key.X;
-
-                if (pixel.Key.Y < minY) minY = pixel.Key.Y;
-                else if (pixel.Key.Y > maxY) maxY = pixel.Key.Y;
-
-                if (clipRequested == false && IsBorderPixel(pixel.Key) && pixel.Value.A == 0)
-                    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()
-        {
-            var 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);
-        }
-
-        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 (var srcContext = LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
-            {
-                var result = BitmapFactory.New(newWidth, newHeight);
-                using (var destContext = result.GetBitmapContext())
-                {
-                    for (int line = 0; line < iteratorHeight; line++)
-                    {
-                        var srcOff = ((offsetYSrc + line) * Width + offsetXSrc) * SizeOfArgb;
-                        var dstOff = ((offsetY + line) * newWidth + offsetX) * SizeOfArgb;
-                        BitmapContext.BlockCopy(srcContext, srcOff, destContext, dstOff, count * SizeOfArgb);
-                    }
-
-                    LayerBitmap = result;
-                    Width = newWidth;
-                    Height = newHeight;
-                }
-            }
-        }
-    }
+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)
+        {
+            if (pixels.ChangedPixels == null || pixels.ChangedPixels.Count == 0)
+            {
+                return;
+            }
+
+            if (dynamicResize)
+            {
+                DynamicResize(pixels);
+            }
+
+            if (applyOffset)
+            {
+                pixels.ChangedPixels = GetRelativePosition(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();
+        }
+
+        /// <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>
+        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))
+            {
+                if (newMaxX + 1 > Width || newMaxY + 1 > Height)
+                {
+                    IncreaseSizeToBottom(newMaxX, newMaxY);
+                }
+
+                if (newMinX < 0 || newMinY < 0)
+                {
+                    IncreaseSizeToTop(newMinX, newMinY);
+                }
+            }
+
+            // if clip is requested
+            if (borderData.Item2)
+            {
+                clipRequested = true;
+            }
+        }
+
+        /// <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;
+                }
+                else if (pixel.Key.X > maxX)
+                {
+                    maxX = pixel.Key.X;
+                }
+
+                if (pixel.Key.Y < minY)
+                {
+                    minY = pixel.Key.Y;
+                }
+                else if (pixel.Key.Y > maxY)
+                {
+                    maxY = pixel.Key.Y;
+                }
+
+                if (clipRequested == false && IsBorderPixel(pixel.Key) && pixel.Value.A == 0)
+                {
+                    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;
+            }
+        }
+
+        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>
+        ///     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;
+                }
+            }
+        }
+    }
 }

+ 76 - 63
PixiEditor/Models/Layers/SerializableLayer.cs

@@ -1,64 +1,77 @@
-using System;
-using System.Linq;
-
-namespace PixiEditor.Models.Layers
-{
-    [Serializable]
-    public class SerializableLayer
-    {
-        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 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 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()
-        {
-            var 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();
-        }
-    }
+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);
+        }
+
+        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)

+ 62 - 47
PixiEditor/Models/Position/CoordinatesCalculator.cs

@@ -2,33 +2,30 @@
 using System.Collections.Generic;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
 
 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;
             }
@@ -38,65 +35,69 @@ namespace PixiEditor.Models.Position
 
         public static Coordinates GetCenterPoint(Coordinates startingPoint, Coordinates endPoint)
         {
-            int x = (int) Math.Floor((startingPoint.X + endPoint.X) / 2f);
-            int y = (int) Math.Floor((startingPoint.Y + endPoint.Y) / 2f);
+            int x = (int)Math.Floor((startingPoint.X + endPoint.X) / 2f);
+            int y = (int)Math.Floor((startingPoint.Y + endPoint.Y) / 2f);
             return new Coordinates(x, y);
         }
 
         /// <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++;
             y2++;
             List<Coordinates> coordinates = new List<Coordinates>();
             for (int y = y1; y < y1 + (y2 - y1); y++)
-            for (int x = x1; x < x1 + (x2 - x1); x++)
-                coordinates.Add(new Coordinates(x, y));
+            {
+                for (int x = x1; x < x1 + (x2 - x1); x++)
+                {
+                    coordinates.Add(new Coordinates(x, y));
+                }
+            }
+
             return coordinates.ToArray();
         }
 
         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));
         }
 
-
         public static int FindMinYNonTransparent(WriteableBitmap bitmap)
         {
             Color transparent = Color.FromArgb(0, 0, 0, 0);
-            using var ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
+            using BitmapContext ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
             for (int y = 0; y < ctx.Height; y++)
-            for (int x = 0; x < ctx.Width; x++)
-                if (ctx.WriteableBitmap.GetPixel(x, y) != transparent)
-                    return y;
+            {
+                for (int x = 0; x < ctx.Width; x++)
+                {
+                    if (ctx.WriteableBitmap.GetPixel(x, y) != transparent)
+                    {
+                        return y;
+                    }
+                }
+            }
 
             return -1;
         }
@@ -104,11 +105,17 @@ namespace PixiEditor.Models.Position
         public static int FindMinXNonTransparent(WriteableBitmap bitmap)
         {
             Color transparent = Color.FromArgb(0, 0, 0, 0);
-            using var ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
+            using BitmapContext ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
             for (int x = 0; x < ctx.Width; x++)
-            for (int y = 0; y < ctx.Height; y++)
-                if (bitmap.GetPixel(x, y) != transparent)
-                    return x;
+            {
+                for (int y = 0; y < ctx.Height; y++)
+                {
+                    if (bitmap.GetPixel(x, y) != transparent)
+                    {
+                        return x;
+                    }
+                }
+            }
 
             return -1;
         }
@@ -117,13 +124,17 @@ namespace PixiEditor.Models.Position
         {
             Color transparent = Color.FromArgb(0, 0, 0, 0);
             bitmap.Lock();
-            for (int y = (int) bitmap.Height - 1; y >= 0; y--)
-            for (int x = (int) bitmap.Width - 1; x >= 0; x--)
-                if (bitmap.GetPixel(x, y) != transparent)
+            for (int y = (int)bitmap.Height - 1; y >= 0; y--)
+            {
+                for (int x = (int)bitmap.Width - 1; x >= 0; x--)
                 {
-                    bitmap.Unlock();
-                    return y;
+                    if (bitmap.GetPixel(x, y) != transparent)
+                    {
+                        bitmap.Unlock();
+                        return y;
+                    }
                 }
+            }
 
             bitmap.Unlock();
             return -1;
@@ -133,13 +144,17 @@ namespace PixiEditor.Models.Position
         {
             Color transparent = Color.FromArgb(0, 0, 0, 0);
             bitmap.Lock();
-            for (int x = (int) bitmap.Width - 1; x >= 0; x--)
-            for (int y = (int) bitmap.Height - 1; y >= 0; y--)
-                if (bitmap.GetPixel(x, y) != transparent)
+            for (int x = (int)bitmap.Width - 1; x >= 0; x--)
+            {
+                for (int y = (int)bitmap.Height - 1; y >= 0; y--)
                 {
-                    bitmap.Unlock();
-                    return x;
+                    if (bitmap.GetPixel(x, y) != transparent)
+                    {
+                        bitmap.Unlock();
+                        return x;
+                    }
                 }
+            }
 
             bitmap.Unlock();
             return -1;

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

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

+ 9 - 12
PixiEditor/Models/Position/MousePositionConverter.cs

@@ -1,22 +1,19 @@
-using System.Runtime.InteropServices;
-using System.Windows;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
+using System.Drawing;
+using System.Runtime.InteropServices;
 
 namespace PixiEditor.Models.Position
 {
     public static class MousePositionConverter
     {
-        public static Coordinates CurrentCoordinates { get; set; }
-
-        [DllImport("user32.dll")]
-        private static extern bool GetCursorPos(out System.Drawing.Point point);
-
-        public static System.Drawing.Point GetCursorPosition()
+        public static Coordinates CurrentCoordinates { get; set; }
+
+        public static Point GetCursorPosition()
         {
-            System.Drawing.Point point;
-            GetCursorPos(out point);
+            GetCursorPos(out Point point);
             return point;
         }
+
+        [DllImport("user32.dll")]
+        private static extern bool GetCursorPos(out Point point);
     }
 }

+ 2 - 1
PixiEditor/Models/Processes/ProcessHelper.cs

@@ -19,7 +19,8 @@ namespace PixiEditor.Models.Processes
             {
                 throw ex;
             }
+
             return proc;
         }
     }
-}
+}

+ 2 - 0
PixiEditor/Models/Tools/BitmapOperationTool.cs

@@ -8,7 +8,9 @@ namespace PixiEditor.Models.Tools
     public abstract class BitmapOperationTool : Tool
     {
         public bool RequiresPreviewLayer { get; set; }
+
         public bool UseDefaultUndoMethod { get; set; } = true;
+
         public abstract LayerChange[] Use(Layer layer, Coordinates[] mouseMove, Color color);
 
         protected LayerChange[] Only(BitmapPixelChanges changes, Layer layer)

+ 38 - 20
PixiEditor/Models/Tools/ShapeTool.cs

@@ -5,15 +5,12 @@ using System.Windows.Media;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 
 namespace PixiEditor.Models.Tools
 {
     public abstract class ShapeTool : BitmapOperationTool
     {
-        public abstract override ToolType ToolType { get; }
-
         public ShapeTool()
         {
             RequiresPreviewLayer = true;
@@ -21,36 +18,57 @@ namespace PixiEditor.Models.Tools
             Toolbar = new BasicShapeToolbar();
         }
 
+        public abstract override ToolType ToolType { get; }
+
         public abstract override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color);
 
         protected IEnumerable<Coordinates> GetThickShape(IEnumerable<Coordinates> shape, int thickness)
         {
             List<Coordinates> output = new List<Coordinates>();
-            foreach(var item in shape)
+            foreach (Coordinates item in shape)
+            {
                 output.AddRange(
                     CoordinatesCalculator.RectangleToCoordinates(
-                        CoordinatesCalculator.CalculateThicknessCenter(item, thickness)));
+                        CoordinatesCalculator.CalculateThicknessCenter(item, thickness)));
+            }
+
             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),
-                    new Coordinates(startingCords.X, startingCords.Y));
-            if (startingCords.X < currentCoordinates.X && startingCords.Y < currentCoordinates.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),
-                    new Coordinates(currentCoordinates.X, startingCords.Y));
-            if (startingCords.X > currentCoordinates.X && startingCords.Y <= currentCoordinates.Y)
-                return new DoubleCords(new Coordinates(currentCoordinates.X, startingCords.Y),
-                    new Coordinates(startingCords.X, currentCoordinates.Y));
+            if (startingCords.X > currentCoordinates.X && startingCords.Y > 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),
+                    new Coordinates(currentCoordinates.X, currentCoordinates.Y));
+            }
+
+            if (startingCords.Y > 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),
+                    new Coordinates(startingCords.X, currentCoordinates.Y));
+            }
+
             return new DoubleCords(startingCords, secondCoordinates);
         }
     }

+ 30 - 10
PixiEditor/Models/Tools/Tool.cs

@@ -7,17 +7,22 @@ namespace PixiEditor.Models.Tools
 {
     public abstract class Tool : NotifyableObject
     {
+        private bool isActive;
+
         public abstract ToolType ToolType { get; }
+
         public string ImagePath => $"/Images/{ToolType}Image.png";
+
         public bool HideHighlight { get; set; } = false;
+
         public string Tooltip { get; set; }
 
         public bool IsActive
         {
-            get => _isActive;
+            get => isActive;
             set
             {
-                _isActive = value;
+                isActive = value;
                 RaisePropertyChanged("IsActive");
             }
         }
@@ -26,15 +31,30 @@ namespace PixiEditor.Models.Tools
 
         public Toolbar Toolbar { get; set; } = new EmptyToolbar();
 
-        private bool _isActive;
         public bool CanStartOutsideCanvas { get; set; } = false;
 
-        public virtual void OnMouseDown(MouseEventArgs e) { }
-
-        public virtual void OnMouseUp(MouseEventArgs e) { }
-
-        public virtual void OnMouseMove(MouseEventArgs e) { }
-
-        public virtual void AfterAddedUndo() { }
+        public virtual void OnMouseDown(MouseEventArgs e)
+        {
+        }
+
+        public virtual void OnMouseUp(MouseEventArgs e)
+        {
+        }
+
+        public virtual void OnRecordingLeftMouseDown(MouseEventArgs e)
+        {
+        }
+
+        public virtual void OnStoppedRecordingMouseUp(MouseEventArgs e)
+        {
+        }
+
+        public virtual void OnMouseMove(MouseEventArgs e)
+        {
+        }
+
+        public virtual void AfterAddedUndo()
+        {
+        }
     }
 }

+ 4 - 2
PixiEditor/Models/Tools/ToolSettings/Settings/BoolSetting.cs

@@ -7,14 +7,16 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
     public class BoolSetting : Setting<bool>
     {
-        public BoolSetting(string name, string label = "") : base(name)
+        public BoolSetting(string name, string label = "")
+            : base(name)
         {
             Label = label;
             Value = false;
             SettingControl = GenerateCheckBox();
         }
 
-        public BoolSetting(string name, bool isChecked, string label = "") : base(name)
+        public BoolSetting(string name, bool isChecked, string label = "")
+            : base(name)
         {
             Label = label;
             Value = isChecked;

+ 2 - 1
PixiEditor/Models/Tools/ToolSettings/Settings/ColorSetting.cs

@@ -6,7 +6,8 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
     public class ColorSetting : Setting<Color>
     {
-        public ColorSetting(string name, string label = "") : base(name)
+        public ColorSetting(string name, string label = "")
+            : base(name)
         {
             Label = label;
             SettingControl = GenerateColorPicker();

+ 6 - 6
PixiEditor/Models/Tools/ToolSettings/Settings/DropdownSetting.cs

@@ -5,19 +5,19 @@ using System.Windows.Data;
 
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
-    public class DropdownSetting : Setting<object>
+    public class DropdownSetting : Setting<ComboBoxItem>
     {
-        public string[] Values { get; set; }
-
-        public DropdownSetting(string name, string[] values, string label) : base(name)
+        public DropdownSetting(string name, string[] values, string label)
+            : base(name)
         {
             Values = values;
             SettingControl = GenerateDropdown();
-            Value = ((ComboBox) SettingControl).Items[0];
+            Value = (ComboBoxItem)((ComboBox)SettingControl).Items[0];
             Label = label;
         }
 
-
+        public string[] Values { get; set; }
+
         private ComboBox GenerateDropdown()
         {
             ComboBox combobox = new ComboBox

+ 43 - 37
PixiEditor/Models/Tools/ToolSettings/Settings/FloatSetting.cs

@@ -1,38 +1,44 @@
-using System.Windows.Data;
-using PixiEditor.Views;
-
-namespace PixiEditor.Models.Tools.ToolSettings.Settings
-{
-    public class FloatSetting : Setting<float>
-    {
-        public float Min { get; set; }
-        public float Max { get; set; }
-
-        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();
-        }
-
-        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;
-        }
-    }
+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; }
+
+        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;
+        }
+    }
 }

+ 44 - 40
PixiEditor/Models/Tools/ToolSettings/Settings/Setting.cs

@@ -1,41 +1,45 @@
-using System.Windows.Controls;
-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
-    {
-        public string Name { get; }
-
-        public string Label { get; set; }
-
-        public bool HasLabel => !string.IsNullOrEmpty(Label);
-
-        public Control SettingControl { get; set; }
-
-        protected Setting(string name)
-        {
-            Name = name;
-        }
-    }
+using System.Windows.Controls;
+using PixiEditor.Helpers;
+
+namespace PixiEditor.Models.Tools.ToolSettings.Settings
+{
+    [System.Diagnostics.CodeAnalysis.SuppressMessage(
+        "StyleCop.CSharp.MaintainabilityRules",
+        "SA1402:File may only contain a single type",
+        Justification = "Same class with generic value")]
+    public abstract class Setting<T> : Setting
+    {
+        protected Setting(string name)
+            : base(name)
+        {
+        }
+
+        public new T Value
+        {
+            get => (T)base.Value;
+            set
+            {
+                base.Value = value;
+                RaisePropertyChanged("Value");
+            }
+        }
+    }
+
+    public abstract class Setting : NotifyableObject
+    {
+        protected Setting(string name)
+        {
+            Name = name;
+        }
+
+        public object Value { get; set; }
+
+        public string Name { get; }
+
+        public string Label { get; set; }
+
+        public bool HasLabel => !string.IsNullOrEmpty(Label);
+
+        public Control SettingControl { get; set; }
+    }
 }

+ 7 - 6
PixiEditor/Models/Tools/ToolSettings/Settings/SizeSetting.cs

@@ -9,7 +9,8 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
     public class SizeSetting : Setting<int>
     {
-        public SizeSetting(string name, string label = null) : base(name)
+        public SizeSetting(string name, string label = null)
+            : base(name)
         {
             Value = 1;
             SettingControl = GenerateTextBox();
@@ -26,11 +27,11 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
                 Height = 20
             };
 
-            if (Application.Current != null)
-            {
-                tb.Style = (Style)Application.Current.TryFindResource("DarkTextBoxStyle");
-            }
-
+            if (Application.Current != null)
+            {
+                tb.Style = (Style)Application.Current.TryFindResource("DarkTextBoxStyle");
+            }
+
             Binding binding = new Binding("Value")
             {
                 Converter = new ToolSizeToIntConverter(),

+ 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
     {

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

@@ -9,7 +9,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
         public BrightnessToolToolbar(float initialValue)
         {
             Settings.Add(new FloatSetting("CorrectionFactor", initialValue, "Strength:", 0f, 100f));
-            Settings.Add(new DropdownSetting("Mode", Enum.GetNames(typeof(BrightnessMode)), "Mode"));
+            Settings.Add(new DropdownSetting("BrightnessMode", Enum.GetNames(typeof(BrightnessMode)), "Mode"));
         }
     }
 }

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

@@ -6,7 +6,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
     {
         public SelectToolToolbar()
         {
-            Settings.Add(new DropdownSetting("Mode", new[] {"New", "Add", "Subtract"}, "Selection type"));
+            Settings.Add(new DropdownSetting("SelectMode", new[] { "New", "Add", "Subtract" }, "Selection type"));
         }
     }
 }

+ 74 - 66
PixiEditor/Models/Tools/ToolSettings/Toolbars/Toolbar.cs

@@ -1,67 +1,75 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-
-namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
-{
-    public abstract class Toolbar
-    {
-        private static readonly List<Setting> SharedSettings = new List<Setting>();
-        public ObservableCollection<Setting> Settings { get; set; } = new ObservableCollection<Setting>();
-
-        /// <summary>
-        ///     Gets setting in toolbar by name.
-        /// </summary>
-        /// <param name="name">Setting name, non case sensitive</param>
-        /// <returns></returns>
-        public virtual Setting GetSetting(string name)
-        {
-            return Settings.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
-        }
-
-        /// <summary>
-        ///     Gets setting of given type T in toolbar by name.
-        /// </summary>
-        /// <param name="name">Setting name, non case sensitive</param>
-        /// <returns></returns>
-        public T GetSetting<T>(string name)
-            where T : Setting
-        {
-            Setting setting =  Settings.FirstOrDefault(currentSetting => string.Equals(currentSetting.Name, name, StringComparison.CurrentCultureIgnoreCase));
-
-            if (setting == null || !(setting is T convertedSetting))
-                return null;
-
-            return convertedSetting;
-        }
-
-        /// <summary>
-        ///     Saves current toolbar state, so other toolbars with common settings can load them.
-        /// </summary>
-        public void SaveToolbarSettings()
-        {
-            foreach (Setting setting in Settings)
-                AddSettingToCollection(SharedSettings, setting);
-        }
-
-        /// <summary>
-        ///     Loads common settings saved from previous tools to current one.
-        /// </summary>
-        public void LoadSharedSettings()
-        {
-            foreach (Setting sharedSetting in SharedSettings)
-                AddSettingToCollection(Settings, sharedSetting);
-        }
-
-        private static void AddSettingToCollection(ICollection<Setting> collection, Setting setting)
-        {
-            Setting storedSetting = collection.FirstOrDefault(currentSetting => currentSetting.Name == setting.Name);
-            if (storedSetting != null)
-                collection.Remove(storedSetting);
-
-            collection.Add(setting);
-        }
-    }
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+
+namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
+{
+    public abstract class Toolbar
+    {
+        private static readonly List<Setting> SharedSettings = new List<Setting>();
+
+        public ObservableCollection<Setting> Settings { get; set; } = new ObservableCollection<Setting>();
+
+        /// <summary>
+        ///     Gets setting in toolbar by name.
+        /// </summary>
+        /// <param name="name">Setting name, non case sensitive.</param>
+        /// <returns></returns>
+        public virtual Setting GetSetting(string name)
+        {
+            return Settings.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
+        }
+
+        /// <summary>
+        ///     Gets setting of given type T in toolbar by name.
+        /// </summary>
+        /// <param name="name">Setting name, non case sensitive.</param>
+        /// <returns></returns>
+        public T GetSetting<T>(string name)
+            where T : Setting
+        {
+            Setting setting = Settings.FirstOrDefault(currentSetting => string.Equals(currentSetting.Name, name, StringComparison.CurrentCultureIgnoreCase));
+
+            if (setting == null || !(setting is T convertedSetting))
+            {
+                return null;
+            }
+
+            return convertedSetting;
+        }
+
+        /// <summary>
+        ///     Saves current toolbar state, so other toolbars with common settings can load them.
+        /// </summary>
+        public void SaveToolbarSettings()
+        {
+            for (int i = 0; i < Settings.Count; i++)
+            {
+                if (SharedSettings.Any(x => x.Name == Settings[i].Name))
+                {
+                    SharedSettings.First(x => x.Name == Settings[i].Name).Value = Settings[i].Value;
+                }
+                else
+                {
+                    SharedSettings.Add(Settings[i]);
+                }
+            }
+        }
+
+        /// <summary>
+        ///     Loads common settings saved from previous tools to current one.
+        /// </summary>
+        public void LoadSharedSettings()
+        {
+            for (int i = 0; i < SharedSettings.Count; i++)
+            {
+                if (Settings.Any(x => x.Name == SharedSettings[i].Name))
+                {
+                    Settings.First(x => x.Name == SharedSettings[i].Name).Value = SharedSettings[i].Value;
+                }
+            }
+        }
+    }
 }

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

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

+ 33 - 23
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -15,22 +15,23 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class BrightnessTool : BitmapOperationTool
     {
-        private const float CorrectionFactor = 5f; //Initial correction factor
-
-        public override ToolType ToolType => ToolType.Brightness;
-        public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
-
-        private List<Coordinates> _pixelsVisited = new List<Coordinates>();
+        private const float CorrectionFactor = 5f; // Initial correction factor
 
+        private 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 void OnMouseDown(MouseEventArgs e)
+        public override ToolType ToolType => ToolType.Brightness;
+
+        public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
+
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
-            _pixelsVisited.Clear();
+            pixelsVisited.Clear();
         }
 
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
@@ -41,37 +42,46 @@ namespace PixiEditor.Models.Tools.Tools
             Mode = mode;
 
             LayerChange[] layersChanges = new LayerChange[1];
-            if (Keyboard.IsKeyDown(Key.LeftCtrl))
-                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor),
-                    layer);
-            else
-                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor),
-                    layer);
+            if (Keyboard.IsKeyDown(Key.LeftCtrl))
+            {
+                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor), layer);
+            }
+            else
+            {
+                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor), layer);
+            }
+
             return layersChanges;
         }
 
-        public BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize,
-            float correctionFactor)
+        public BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
         {
             DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
-            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(centeredCoords.Coords1.X,
+            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(
+                centeredCoords.Coords1.X,
                 centeredCoords.Coords1.Y,
-                centeredCoords.Coords2.X, centeredCoords.Coords2.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]);
+                    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),
+                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),
+                changes.ChangedPixels.Add(
+                    new Coordinates(rectangleCoordinates[i].X, rectangleCoordinates[i].Y),
                     newColor);
             }
 

+ 47 - 35
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -12,13 +12,13 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class CircleTool : ShapeTool
     {
-        public override ToolType ToolType => ToolType.Circle;
-
         public CircleTool()
         {
             Tooltip = "Draws circle on canvas (C). Hold Shift to draw even circle.";
         }
 
+        public override ToolType ToolType => ToolType.Circle;
+
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
@@ -33,19 +33,18 @@ namespace PixiEditor.Models.Tools.Tools
                         .ChangedPixels);
             }
 
-            return new[] {new LayerChange(pixels, layer)};
+            return new[] { new LayerChange(pixels, layer) };
         }
 
         /// <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);
@@ -58,14 +57,14 @@ namespace PixiEditor.Models.Tools.Tools
 
             return output;
         }
+
         /// <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;
@@ -76,20 +75,27 @@ namespace PixiEditor.Models.Tools.Tools
             List<Coordinates> output = new List<Coordinates>();
             IEnumerable<Coordinates> ellipse = MidpointEllipse(radiusX, radiusY, centerX, centerY);
             if (thickness == 1)
+            {
                 output.AddRange(ellipse);
+            }
             else
+            {
                 output.AddRange(GetThickShape(ellipse, thickness));
+            }
+
             return output.Distinct();
         }
 
         public IEnumerable<Coordinates> MidpointEllipse(double halfWidth, double halfHeight, double centerX, double centerY)
         {
             if (halfWidth < 1 || halfHeight < 1)
+            {
                 return FallbackRectangle(halfWidth, halfHeight, centerX, centerY);
+            }
 
-            //ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
+            // ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
 
-            //Make sure we are always at the center of a pixel
+            // Make sure we are always at the center of a pixel
             double currentX = Math.Ceiling(centerX - 0.5) + 0.5;
             double currentY = centerY + halfHeight;
 
@@ -97,37 +103,37 @@ namespace PixiEditor.Models.Tools.Tools
 
             double currentSlope;
 
-            //from PI/2 to middle
+            // from PI/2 to middle
             do
             {
                 outputCoordinates.AddRange(GetRegionPoints(currentX, centerX, currentY, centerY));
 
-                //calculate next pixel coords
+                // 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--;
                 }
 
-                //calculate how far we've advanced
+                // calculate how far we've advanced
                 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
+            // from middle to 0
             while (currentY - centerY >= 0)
             {
                 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++;
                 }
@@ -144,11 +150,13 @@ namespace PixiEditor.Models.Tools.Tools
                 coordinates.Add(new Coordinates((int)x, (int)(centerY - halfHeight)));
                 coordinates.Add(new Coordinates((int)x, (int)(centerY + halfHeight)));
             }
+
             for (double y = centerY - halfHeight + 1; y <= centerY + halfHeight - 1; y++)
             {
                 coordinates.Add(new Coordinates((int)(centerX - halfWidth), (int)y));
                 coordinates.Add(new Coordinates((int)(centerX + halfWidth), (int)y));
             }
+
             return coordinates.ToArray();
         }
 
@@ -159,21 +167,25 @@ namespace PixiEditor.Models.Tools.Tools
             int top = outlineCoordinates.Min(x => x.Y);
             for (int i = top + 1; i < bottom; i++)
             {
-                var rowCords = outlineCoordinates.Where(x => x.Y == i);
+                IEnumerable<Coordinates> rowCords = outlineCoordinates.Where(x => x.Y == i);
                 int right = rowCords.Max(x => x.X);
                 int left = rowCords.Min(x => x.X);
-                for (int j = left + 1; j < right; j++) finalCoordinates.Add(new Coordinates(j, i));
+                for (int j = left + 1; j < right; j++)
+                {
+                    finalCoordinates.Add(new Coordinates(j, i));
+                }
             }
 
             return finalCoordinates;
         }
+
         private Coordinates[] GetRegionPoints(double x, double xc, double y, double yc)
         {
             Coordinates[] outputCoordinates = new Coordinates[4];
-            outputCoordinates[0] = new Coordinates((int)Math.Floor(x),                  (int)Math.Floor(y));
-            outputCoordinates[1] = new Coordinates((int)Math.Floor((-(x - xc) + xc)),   (int)Math.Floor(y));
-            outputCoordinates[2] = new Coordinates((int)Math.Floor(x),                  (int)Math.Floor((-(y - yc) + yc)));
-            outputCoordinates[3] = new Coordinates((int)Math.Floor((-(x - xc) + xc)),   (int)Math.Floor((-(y - yc) + yc)));
+            outputCoordinates[0] = new Coordinates((int)Math.Floor(x), (int)Math.Floor(y));
+            outputCoordinates[1] = new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(y));
+            outputCoordinates[2] = new Coordinates((int)Math.Floor(x), (int)Math.Floor(-(y - yc) + yc));
+            outputCoordinates[3] = new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(-(y - yc) + yc));
             return outputCoordinates;
         }
     }

+ 5 - 6
PixiEditor/Models/Tools/Tools/ColorPickerTool.cs

@@ -7,14 +7,14 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class ColorPickerTool : ReadonlyTool
     {
-        public override ToolType ToolType => ToolType.ColorPicker;
-
         public ColorPickerTool()
         {
             HideHighlight = true;
             Tooltip = "Swaps primary color with selected on canvas. (O)";
         }
 
+        public override ToolType ToolType => ToolType.ColorPicker;
+
         public override void Use(Coordinates[] coordinates)
         {
             ViewModelMain.Current.PrimaryColor = GetColorUnderMouse();
@@ -23,12 +23,11 @@ namespace PixiEditor.Models.Tools.Tools
         public Color GetColorUnderMouse()
         {
             System.Drawing.Color color;
-            using (var bitmap = new Bitmap(1, 1))
+            using (Bitmap bitmap = new Bitmap(1, 1))
             {
-                using (var graphics = Graphics.FromImage(bitmap))
+                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);

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

@@ -9,14 +9,14 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class EraserTool : BitmapOperationTool
     {
-        public override ToolType ToolType => ToolType.Eraser;
-
         public EraserTool()
         {
             Tooltip = "Erasers color from pixel (E)";
             Toolbar = new BasicToolbar();
         }
 
+        public override ToolType ToolType => ToolType.Eraser;
+
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             return Erase(layer, coordinates, Toolbar.GetSetting<SizeSetting>("ToolSize").Value);
@@ -26,8 +26,8 @@ namespace PixiEditor.Models.Tools.Tools
         {
             Coordinates startingCords = coordinates.Length > 1 ? coordinates[1] : coordinates[0];
             PenTool pen = new PenTool();
-            var pixels = pen.Draw(startingCords, coordinates[0], System.Windows.Media.Colors.Transparent, toolSize);
-            return new[] {new LayerChange(pixels, layer)};
+            BitmapPixelChanges pixels = pen.Draw(startingCords, coordinates[0], System.Windows.Media.Colors.Transparent, toolSize);
+            return new[] { new LayerChange(pixels, layer) };
         }
     }
 }

+ 40 - 28
PixiEditor/Models/Tools/Tools/FloodFill.cs

@@ -1,7 +1,5 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Windows.Media;
-using System.Windows.Media.Imaging;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
@@ -10,55 +8,69 @@ using PixiEditor.ViewModels;
 namespace PixiEditor.Models.Tools.Tools
 {
     public class FloodFill : BitmapOperationTool
-    {
-        public override ToolType ToolType => ToolType.Bucket;
-
+    {
         public FloodFill()
         {
             Tooltip = "Fills area with color (G)";
         }
 
+        public override ToolType ToolType => ToolType.Bucket;
+
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             return Only(ForestFire(layer, coordinates[0], color), layer);
         }
 
-        public BitmapPixelChanges ForestFire(Layer layer, Coordinates startingCoords, Color newColor)
+        private BitmapPixelChanges ForestFire(Layer layer, Coordinates startingCoords, Color newColor)
         {
             List<Coordinates> changedCoords = new List<Coordinates>();
 
-            Layer clone = layer.Clone();
             int width = ViewModelMain.Current.BitmapManager.ActiveDocument.Width;
             int height = ViewModelMain.Current.BitmapManager.ActiveDocument.Height;
 
+            var visited = new bool[width, height];
+
             Color colorToReplace = layer.GetPixelWithOffset(startingCoords.X, startingCoords.Y);
 
             var stack = new Stack<Coordinates>();
             stack.Push(new Coordinates(startingCoords.X, startingCoords.Y));
-            
-            using(clone.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadWrite))
-            {
-                while (stack.Count > 0)
-                {
-                    var cords = stack.Pop();
-                    var relativeCords = clone.GetRelativePosition(cords);
 
-                    if (cords.X < 0 || cords.X > width - 1) continue;
-                    if (cords.Y < 0 || cords.Y > height - 1) continue;
-                    if (clone.GetPixel(relativeCords.X, relativeCords.Y) == newColor) continue;
+            while (stack.Count > 0)
+            {
+                var cords = stack.Pop();
+                var relativeCords = layer.GetRelativePosition(cords);
 
-                    if (clone.GetPixel(relativeCords.X, relativeCords.Y) == colorToReplace)
-                    {
-                        changedCoords.Add(new Coordinates(cords.X, cords.Y));
-                        clone.SetPixel(new Coordinates(cords.X, cords.Y), newColor);
-                        stack.Push(new Coordinates(cords.X, cords.Y - 1));
-                        stack.Push(new Coordinates(cords.X + 1, cords.Y));
-                        stack.Push(new Coordinates(cords.X, cords.Y + 1));
-                        stack.Push(new Coordinates(cords.X - 1, cords.Y));
-                    }
+                if (cords.X < 0 || cords.X > width - 1)
+                {
+                    continue;
+                }
+
+                if (cords.Y < 0 || cords.Y > height - 1)
+                {
+                    continue;
+                }
+
+                if (visited[cords.X, cords.Y])
+                {
+                    continue;
+                }
+
+                if (layer.GetPixel(relativeCords.X, relativeCords.Y) == newColor)
+                {
+                    continue;
+                }
+
+                if (layer.GetPixel(relativeCords.X, relativeCords.Y) == colorToReplace)
+                {
+                    changedCoords.Add(new Coordinates(cords.X, cords.Y));
+                    visited[cords.X, cords.Y] = true;
+                    stack.Push(new Coordinates(cords.X, cords.Y - 1));
+                    stack.Push(new Coordinates(cords.X, cords.Y + 1));
+                    stack.Push(new Coordinates(cords.X - 1, cords.Y));
+                    stack.Push(new Coordinates(cords.X + 1, cords.Y));
                 }
-
             }
+
             return BitmapPixelChanges.FromSingleColoredArray(changedCoords, newColor);
         }
     }

+ 34 - 25
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -12,20 +12,23 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class LineTool : ShapeTool
     {
-        public override ToolType ToolType => ToolType.Line;
-
         public LineTool()
         {
             Tooltip = "Draws line on canvas (L). Hold Shift to draw even line.";
             Toolbar = new BasicToolbar();
         }
 
+        public override ToolType ToolType => ToolType.Line;
+
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
-            var pixels =
+            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,10 +37,9 @@ 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);
+            return CreateLine(new[] { end, start }, thickness, startCap, endCap);
         }
 
         private IEnumerable<Coordinates> CreateLine(Coordinates[] coordinates, int thickness, CapType startCap, CapType endCap)
@@ -45,28 +47,32 @@ namespace PixiEditor.Models.Tools.Tools
             Coordinates startingCoordinates = coordinates[^1];
             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);
         }
 
         private IEnumerable<Coordinates> GetLinePoints(Coordinates start, Coordinates end, int thickness, CapType startCap, CapType endCap)
         {
-            var startingCap = GetCapCoordinates(startCap, start, thickness);
-            if (start == end) return startingCap;
+            IEnumerable<Coordinates> startingCap = GetCapCoordinates(startCap, start, thickness);
+            if (start == end)
+            {
+                return startingCap;
+            }
 
-            var line = BresenhamLine(start.X, start.Y, end.X, end.Y);
+            IEnumerable<Coordinates> line = BresenhamLine(start.X, start.Y, end.X, end.Y);
 
             List<Coordinates> output = new List<Coordinates>(startingCap);
 
             output.AddRange(GetCapCoordinates(endCap, end, thickness));
             if (line.Count() > 2)
             {
-                output.AddRange(GetThickShape(line.Except(new []{start,end}).ToArray(), thickness));
+                output.AddRange(GetThickShape(line.Except(new[] { start, end }).ToArray(), thickness));
             }
 
             return output.Distinct();
-
         }
 
         private IEnumerable<Coordinates> GetCapCoordinates(CapType cap, Coordinates position, int thickness)
@@ -74,24 +80,24 @@ namespace PixiEditor.Models.Tools.Tools
             switch (cap)
             {
                 case CapType.Round:
-                {
-                    return GetRoundCap(position, thickness); // Round cap is not working very well, circle tool must be improved
-                }
-                default: 
+                    {
+                        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();
-            var rectangleCords = CoordinatesCalculator.RectangleToCoordinates(
+            Coordinates[] rectangleCords = CoordinatesCalculator.RectangleToCoordinates(
                 CoordinatesCalculator.CalculateThicknessCenter(position, thickness));
             return circle.CreateEllipse(rectangleCords[0], rectangleCords[^1], 1, true);
         }
@@ -99,7 +105,10 @@ namespace PixiEditor.Models.Tools.Tools
         private IEnumerable<Coordinates> BresenhamLine(int x1, int y1, int x2, int y2)
         {
             List<Coordinates> coordinates = new List<Coordinates>();
-            if (x1 == x2 && y1 == y2) return new[] {new Coordinates(x1, y1)};
+            if (x1 == x2 && y1 == y2)
+            {
+                return new[] { new Coordinates(x1, y1) };
+            }
 
             int d, dx, dy, ai, bi, xi, yi;
             int x = x1, y = y1;

+ 103 - 82
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -18,19 +18,16 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class MoveTool : BitmapOperationTool
     {
-        public bool MoveAll { get; set; } = false;
-
-        public override ToolType ToolType => ToolType.Move;
-        private Layer[] _affectedLayers;
-        private Dictionary<Layer, bool> _clearedPixels = new Dictionary<Layer, bool>();
-        private Coordinates[] _currentSelection;
-        private Coordinates _lastMouseMove;
-        private Coordinates _lastStartMousePos;
-        private Dictionary<Layer, Color[]> _startPixelColors;
-        private Dictionary<Layer, Thickness> _startingOffsets;
-        private Coordinates[] _startSelection;
-        private bool _updateViewModelSelection = true;
-
+        private Layer[] affectedLayers;
+        private Dictionary<Layer, bool> clearedPixels = new Dictionary<Layer, bool>();
+        private Coordinates[] currentSelection;
+        private Coordinates lastMouseMove;
+        private Coordinates lastStartMousePos;
+        private Dictionary<Layer, Color[]> startPixelColors;
+        private Dictionary<Layer, Thickness> startingOffsets;
+        private Coordinates[] startSelection;
+        private bool updateViewModelSelection = true;
+
         public MoveTool()
         {
             Tooltip = "Moves selected pixels (V). Hold Ctrl to move all layers";
@@ -38,95 +35,130 @@ namespace PixiEditor.Models.Tools.Tools
             HideHighlight = true;
             RequiresPreviewLayer = true;
             UseDefaultUndoMethod = true;
-        }
+        }
+
+        public bool MoveAll { get; set; } = false;
+
+        public override ToolType ToolType => ToolType.Move;
 
         public override void AfterAddedUndo()
         {
-            if (_currentSelection != null && _currentSelection.Length != 0)
+            if (currentSelection != null && currentSelection.Length != 0)
             {
-                //Inject to default undo system change custom changes made by this tool
-                foreach (var item in _startPixelColors)
+                // Inject to default undo system change custom changes made by this tool
+                foreach (var item in startPixelColors)
                 {
-                    BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(_startSelection, item.Value);
+                    BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
                     Change changes = UndoManager.UndoStack.Peek();
                     int layerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.IndexOf(item.Key);
 
-                    ((LayerChange[]) changes.OldValue).First(x=> x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
+                    ((LayerChange[])changes.OldValue).First(x => x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
                         .AddRangeOverride(beforeMovePixels.ChangedPixels);
 
-                    ((LayerChange[]) changes.NewValue).First(x => x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
+                    ((LayerChange[])changes.NewValue).First(x => x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
                         .AddRangeNewOnly(BitmapPixelChanges
-                            .FromSingleColoredArray(_startSelection, System.Windows.Media.Colors.Transparent)
+                            .FromSingleColoredArray(startSelection, System.Windows.Media.Colors.Transparent)
                             .ChangedPixels);
                 }
             }
         }
 
-        public override void OnMouseUp(MouseEventArgs e) //This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
-        {   //is because it doesn't fire if no pixel changes were made.
-            if (_currentSelection != null && _currentSelection.Length == 0)
-            {
-                UndoManager.AddUndoChange(new Change(ApplyOffsets, new object[]{_startingOffsets}, 
-                    ApplyOffsets, new object[] { GetOffsets(_affectedLayers)}, "Move layers"));
-            }
-        }
-
-        private void ApplyOffsets(object[] parameters)
+        // This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
+        // is because it doesn't fire if no pixel changes were made.
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
         {
-            Dictionary<Layer, Thickness> offsets = (Dictionary<Layer, Thickness>)parameters[0];
-            foreach (var offset in offsets)
+            if (currentSelection != null && currentSelection.Length == 0)
             {
-                offset.Key.Offset = offset.Value;
+                UndoManager.AddUndoChange(new Change(
+                    ApplyOffsets,
+                    new object[] { startingOffsets },
+                    ApplyOffsets,
+                    new object[] { GetOffsets(affectedLayers) },
+                    "Move layers"));
             }
         }
 
         public override LayerChange[] Use(Layer layer, Coordinates[] mouseMove, Color color)
         {
             Coordinates start = mouseMove[^1];
-            if (_lastStartMousePos != start) //I am aware that this could be moved to OnMouseDown, but it is executed before Use, so I didn't want to complicate for now
+
+            // I am aware that this could be moved to OnMouseDown, but it is executed before Use, so I didn't want to complicate for now
+            if (lastStartMousePos != start)
             {
                 ResetSelectionValues(start);
-                if (ViewModelMain.Current.ActiveSelection != null && ViewModelMain.Current.ActiveSelection.SelectedPoints.Count > 0) //Move offset if no selection
+
+                // Move offset if no selection
+                if (ViewModelMain.Current.ActiveSelection != null && ViewModelMain.Current.ActiveSelection.SelectedPoints.Count > 0)
                 {
-                    _currentSelection = ViewModelMain.Current.ActiveSelection.SelectedPoints.ToArray();
+                    currentSelection = ViewModelMain.Current.ActiveSelection.SelectedPoints.ToArray();
                 }
                 else
                 {
-                    _currentSelection = Array.Empty<Coordinates>();
+                    currentSelection = Array.Empty<Coordinates>();
                 }
 
-                if (Keyboard.IsKeyDown(Key.LeftCtrl) || MoveAll)
-                    _affectedLayers = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Where(x => x.IsVisible)
-                        .ToArray();
-                else
-                    _affectedLayers = new[] {layer};
-
-                _startSelection = _currentSelection;
-                _startPixelColors = BitmapUtils.GetPixelsForSelection(_affectedLayers, _startSelection);
-                _startingOffsets = GetOffsets(_affectedLayers);
+                if (Keyboard.IsKeyDown(Key.LeftCtrl) || MoveAll)
+                {
+                    affectedLayers = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Where(x => x.IsVisible)
+                        .ToArray();
+                }
+                else
+                {
+                    affectedLayers = new[] { layer };
+                }
+
+                startSelection = currentSelection;
+                startPixelColors = BitmapUtils.GetPixelsForSelection(affectedLayers, startSelection);
+                startingOffsets = GetOffsets(affectedLayers);
             }
 
-            LayerChange[] result = new LayerChange[_affectedLayers.Length];
+            LayerChange[] result = new LayerChange[affectedLayers.Length];
             var end = mouseMove[0];
-            for (int i = 0; i < _affectedLayers.Length; i++)
+            for (int i = 0; i < affectedLayers.Length; i++)
             {
-                if (_currentSelection.Length > 0)
+                if (currentSelection.Length > 0)
                 {
-                    var changes = MoveSelection(_affectedLayers[i], mouseMove);
+                    var changes = MoveSelection(affectedLayers[i], mouseMove);
                     changes = RemoveTransparentPixels(changes);
 
-                    result[i] = new LayerChange(changes, _affectedLayers[i]);
+                    result[i] = new LayerChange(changes, affectedLayers[i]);
                 }
                 else
                 {
-                    var vector = Transform.GetTranslation(_lastMouseMove, end);
-                    _affectedLayers[i].Offset = new Thickness(_affectedLayers[i].OffsetX + vector.X, _affectedLayers[i].OffsetY + vector.Y, 0, 0);
-                    result[i] = new LayerChange(BitmapPixelChanges.Empty, _affectedLayers[i]);
+                    var vector = Transform.GetTranslation(lastMouseMove, end);
+                    affectedLayers[i].Offset = new Thickness(affectedLayers[i].OffsetX + vector.X, affectedLayers[i].OffsetY + vector.Y, 0, 0);
+                    result[i] = new LayerChange(BitmapPixelChanges.Empty, affectedLayers[i]);
                 }
             }
-            _lastMouseMove = end;
+
+            lastMouseMove = end;
 
             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];
+            foreach (var offset in offsets)
+            {
+                offset.Key.Offset = offset.Value;
+            }
         }
 
         private Dictionary<Layer, Thickness> GetOffsets(Layer[] layers)
@@ -142,50 +174,39 @@ namespace PixiEditor.Models.Tools.Tools
 
         private BitmapPixelChanges RemoveTransparentPixels(BitmapPixelChanges pixels)
         {
-            foreach (var item in pixels.ChangedPixels.Where(x => x.Value.A == 0).ToList())
-                pixels.ChangedPixels.Remove(item.Key);
+            foreach (var item in pixels.ChangedPixels.Where(x => x.Value.A == 0).ToList())
+            {
+                pixels.ChangedPixels.Remove(item.Key);
+            }
+
             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;
-            _lastMouseMove = start;
-            _clearedPixels = new Dictionary<Layer, bool>();
-            _updateViewModelSelection = true;
-            _startPixelColors = null;
-            _startSelection = null;
+            lastStartMousePos = start;
+            lastMouseMove = start;
+            clearedPixels = new Dictionary<Layer, bool>();
+            updateViewModelSelection = true;
+            startPixelColors = null;
+            startSelection = null;
         }
 
         private Coordinates[] TranslateSelection(Coordinates end, out Coordinates[] previousSelection)
         {
-            Coordinates translation = Transform.GetTranslation(_lastMouseMove, end);
-            previousSelection = _currentSelection.ToArray();
+            Coordinates translation = Transform.GetTranslation(lastMouseMove, end);
+            previousSelection = currentSelection.ToArray();
             return Transform.Translate(previousSelection, translation);
         }
 
         private void ClearSelectedPixels(Layer layer, Coordinates[] selection)
         {
-            if (!_clearedPixels.ContainsKey(layer) || _clearedPixels[layer] == false)
+            if (!clearedPixels.ContainsKey(layer) || clearedPixels[layer] == false)
             {
                 ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.First(x => x == layer)
                     .SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, System.Windows.Media.Colors.Transparent));
 
-                _clearedPixels[layer] = true;
+                clearedPixels[layer] = true;
             }
         }
     }

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

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

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

@@ -10,21 +10,22 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class PenTool : BitmapOperationTool
     {
-        public override ToolType ToolType => ToolType.Pen;
-        private readonly SizeSetting _toolSizeSetting;
+        private readonly SizeSetting toolSizeSetting;
 
         public PenTool()
         {
             Cursor = Cursors.Pen;
             Tooltip = "Standard brush (B)";
             Toolbar = new BasicToolbar();
-            _toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
+            toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
         }
 
+        public override ToolType ToolType => ToolType.Pen;
+
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             Coordinates startingCords = coordinates.Length > 1 ? coordinates[1] : coordinates[0];
-            var pixels = Draw(startingCords, coordinates[0], color, _toolSizeSetting.Value);
+            BitmapPixelChanges pixels = Draw(startingCords, coordinates[0], color, toolSizeSetting.Value);
             return Only(pixels, layer);
         }
 

+ 45 - 33
PixiEditor/Models/Tools/Tools/RectangleTool.cs

@@ -12,14 +12,15 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class RectangleTool : ShapeTool
     {
-        public override ToolType ToolType => ToolType.Rectangle;
-        public bool Filled { get; set; } = false;
-
         public RectangleTool()
         {
             Tooltip = "Draws rectangle on canvas (R). Hold Shift to draw square.";
         }
 
+        public override ToolType ToolType => ToolType.Rectangle;
+
+        public bool Filled { get; set; } = false;
+
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
@@ -29,12 +30,12 @@ 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);
             }
 
-            return new[] {new LayerChange(pixels, layer)};
+            return new[] { new LayerChange(pixels, layer) };
         }
 
         public IEnumerable<Coordinates> CreateRectangle(Coordinates[] coordinates, int thickness)
@@ -44,45 +45,31 @@ namespace PixiEditor.Models.Tools.Tools
             IEnumerable<Coordinates> rectangle = CalculateRectanglePoints(fixedCoordinates);
             output.AddRange(rectangle);
 
-            for (int i = 1; i < (int) Math.Floor(thickness / 2f) + 1; i++)
+            for (int i = 1; i < (int)Math.Floor(thickness / 2f) + 1; i++)
+            {
                 output.AddRange(CalculateRectanglePoints(new DoubleCords(
                     new Coordinates(fixedCoordinates.Coords1.X - i, fixedCoordinates.Coords1.Y - i),
                     new Coordinates(fixedCoordinates.Coords2.X + i, fixedCoordinates.Coords2.Y + i))));
-            for (int i = 1; i < (int) Math.Ceiling(thickness / 2f); i++)
+            }
+
+            for (int i = 1; i < (int)Math.Ceiling(thickness / 2f); i++)
+            {
                 output.AddRange(CalculateRectanglePoints(new DoubleCords(
                     new Coordinates(fixedCoordinates.Coords1.X + i, fixedCoordinates.Coords1.Y + i),
                     new Coordinates(fixedCoordinates.Coords2.X - i, fixedCoordinates.Coords2.Y - i))));
+            }
 
             return output.Distinct();
         }
 
         public IEnumerable<Coordinates> CreateRectangle(Coordinates start, Coordinates end, int thickness)
         {
-            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;
+            return CreateRectangle(new[] { end, start }, thickness);
         }
 
         public IEnumerable<Coordinates> CalculateFillForRectangle(Coordinates start, Coordinates end, int thickness)
         {
-            int offset = (int) Math.Ceiling(thickness / 2f);
+            int offset = (int)Math.Ceiling(thickness / 2f);
             DoubleCords fixedCords = CalculateCoordinatesForShapeRotation(start, end);
 
             DoubleCords innerCords = new DoubleCords
@@ -94,17 +81,42 @@ namespace PixiEditor.Models.Tools.Tools
             int height = innerCords.Coords2.Y - innerCords.Coords1.Y;
             int width = innerCords.Coords2.X - innerCords.Coords1.X;
 
-            if (height < 1 || width < 1) return Array.Empty<Coordinates>();
+            if (height < 1 || width < 1)
+            {
+                return Array.Empty<Coordinates>();
+            }
+
             Coordinates[] filledCoordinates = new Coordinates[width * height];
             int i = 0;
             for (int y = 0; y < height; y++)
-            for (int x = 0; x < width; x++)
             {
-                filledCoordinates[i] = new Coordinates(innerCords.Coords1.X + x, innerCords.Coords1.Y + y);
-                i++;
+                for (int x = 0; x < width; x++)
+                {
+                    filledCoordinates[i] = new Coordinates(innerCords.Coords1.X + x, innerCords.Coords1.Y + y);
+                    i++;
+                }
             }
 
             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;
+        }
     }
 }

Some files were not shown because too many files changed in this diff