Browse Source

Merge pull request #180 from PixiEditor/master

Version 0.1.5.0
Krzysztof Krysiński 4 years ago
parent
commit
dc766483ff
100 changed files with 1589 additions and 373 deletions
  1. 3 0
      .github/FUNDING.yml
  2. 62 0
      Custom.ruleset
  3. BIN
      PixiEditor.MSIX/Images/LargeTile.scale-100.png
  4. BIN
      PixiEditor.MSIX/Images/LargeTile.scale-125.png
  5. BIN
      PixiEditor.MSIX/Images/LargeTile.scale-150.png
  6. BIN
      PixiEditor.MSIX/Images/LargeTile.scale-200.png
  7. BIN
      PixiEditor.MSIX/Images/LargeTile.scale-400.png
  8. BIN
      PixiEditor.MSIX/Images/LockScreenLogo.scale-200.png
  9. BIN
      PixiEditor.MSIX/Images/SmallTile.scale-100.png
  10. BIN
      PixiEditor.MSIX/Images/SmallTile.scale-125.png
  11. BIN
      PixiEditor.MSIX/Images/SmallTile.scale-150.png
  12. BIN
      PixiEditor.MSIX/Images/SmallTile.scale-200.png
  13. BIN
      PixiEditor.MSIX/Images/SmallTile.scale-400.png
  14. BIN
      PixiEditor.MSIX/Images/SplashScreen.scale-100.png
  15. BIN
      PixiEditor.MSIX/Images/SplashScreen.scale-125.png
  16. BIN
      PixiEditor.MSIX/Images/SplashScreen.scale-150.png
  17. BIN
      PixiEditor.MSIX/Images/SplashScreen.scale-200.png
  18. BIN
      PixiEditor.MSIX/Images/SplashScreen.scale-400.png
  19. BIN
      PixiEditor.MSIX/Images/Square150x150Logo.scale-100.png
  20. BIN
      PixiEditor.MSIX/Images/Square150x150Logo.scale-125.png
  21. BIN
      PixiEditor.MSIX/Images/Square150x150Logo.scale-150.png
  22. BIN
      PixiEditor.MSIX/Images/Square150x150Logo.scale-200.png
  23. BIN
      PixiEditor.MSIX/Images/Square150x150Logo.scale-400.png
  24. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-16.png
  25. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-24.png
  26. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-256.png
  27. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-32.png
  28. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-48.png
  29. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-unplated_targetsize-16.png
  30. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-unplated_targetsize-256.png
  31. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-unplated_targetsize-32.png
  32. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.altform-unplated_targetsize-48.png
  33. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.scale-100.png
  34. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.scale-125.png
  35. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.scale-150.png
  36. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.scale-200.png
  37. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.scale-400.png
  38. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.targetsize-16.png
  39. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.targetsize-24.png
  40. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.targetsize-24_altform-unplated.png
  41. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.targetsize-256.png
  42. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.targetsize-32.png
  43. BIN
      PixiEditor.MSIX/Images/Square44x44Logo.targetsize-48.png
  44. BIN
      PixiEditor.MSIX/Images/StoreLogo.backup.png
  45. BIN
      PixiEditor.MSIX/Images/StoreLogo.scale-100.png
  46. BIN
      PixiEditor.MSIX/Images/StoreLogo.scale-125.png
  47. BIN
      PixiEditor.MSIX/Images/StoreLogo.scale-150.png
  48. BIN
      PixiEditor.MSIX/Images/StoreLogo.scale-200.png
  49. BIN
      PixiEditor.MSIX/Images/StoreLogo.scale-400.png
  50. BIN
      PixiEditor.MSIX/Images/Wide310x150Logo.scale-100.png
  51. BIN
      PixiEditor.MSIX/Images/Wide310x150Logo.scale-125.png
  52. BIN
      PixiEditor.MSIX/Images/Wide310x150Logo.scale-150.png
  53. BIN
      PixiEditor.MSIX/Images/Wide310x150Logo.scale-200.png
  54. BIN
      PixiEditor.MSIX/Images/Wide310x150Logo.scale-400.png
  55. 66 0
      PixiEditor.MSIX/Package.appxmanifest
  56. 129 0
      PixiEditor.MSIX/PixiEditor.MSIX.wapproj
  57. 233 9
      PixiEditor.sln
  58. 7 0
      PixiEditor/App.xaml
  59. 1 6
      PixiEditor/Exceptions/CorruptedFileException.cs
  60. 3 6
      PixiEditor/Helpers/AssemblyHelper.cs
  61. 4 5
      PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs
  62. 38 0
      PixiEditor/Helpers/Behaviours/GlobalShortcutFocusBehavior.cs
  63. 62 62
      PixiEditor/Helpers/Behaviours/MouseBehavior.cs
  64. 1 0
      PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs
  65. 24 0
      PixiEditor/Helpers/Converters/BoolToBrushConverter.cs
  66. 53 0
      PixiEditor/Helpers/Converters/BrushTuple.cs
  67. 1 1
      PixiEditor/Helpers/Converters/DoubleToIntConverter.cs
  68. 26 0
      PixiEditor/Helpers/Converters/EmptyStringToVisibiltyConverter.cs
  69. 44 0
      PixiEditor/Helpers/Converters/FileExtensionToImageSourceConverter.cs
  70. 19 0
      PixiEditor/Helpers/Converters/FloorConverter.cs
  71. 20 0
      PixiEditor/Helpers/Converters/IntToPickerTypeConverter.cs
  72. 24 26
      PixiEditor/Helpers/Converters/IntToViewportRectConverter.cs
  73. 35 0
      PixiEditor/Helpers/Converters/KeyToStringConverter.cs
  74. 26 0
      PixiEditor/Helpers/Converters/NotNullToBoolConverter.cs
  75. 26 0
      PixiEditor/Helpers/Converters/ThresholdVisibilityConverter.cs
  76. 59 0
      PixiEditor/Helpers/CrashHelper.cs
  77. 17 0
      PixiEditor/Helpers/Extensions/EnumHelpers.cs
  78. 16 0
      PixiEditor/Helpers/Extensions/ObservableCollectionEx.cs
  79. 8 1
      PixiEditor/Helpers/Extensions/ParserHelpers.cs
  80. 27 0
      PixiEditor/Helpers/Extensions/StringHelpers.cs
  81. 15 0
      PixiEditor/Helpers/Extensions/ToolbarHelpers.cs
  82. 82 0
      PixiEditor/Helpers/InputKeyHelpers.cs
  83. 43 0
      PixiEditor/Helpers/SelectionHelpers.cs
  84. 13 0
      PixiEditor/Helpers/StringExtensions.cs
  85. 28 0
      PixiEditor/Helpers/UI/DocumentsTemplateSelector.cs
  86. 20 0
      PixiEditor/Helpers/UI/PanelsStyleSelector.cs
  87. BIN
      PixiEditor/Images/JpgFile.png
  88. BIN
      PixiEditor/Images/PixiBotLogo.png
  89. BIN
      PixiEditor/Images/PixiFile.png
  90. BIN
      PixiEditor/Images/PixiParserLogo.png
  91. BIN
      PixiEditor/Images/PngFile.png
  92. BIN
      PixiEditor/Images/UnknownFile.png
  93. 5 5
      PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs
  94. 22 6
      PixiEditor/Models/Controllers/BitmapManager.cs
  95. 262 197
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  96. 29 29
      PixiEditor/Models/Controllers/ClipboardController.cs
  97. 3 3
      PixiEditor/Models/Controllers/LayersChangedEventArgs.cs
  98. 12 12
      PixiEditor/Models/Controllers/PixelChangesController.cs
  99. 16 1
      PixiEditor/Models/Controllers/Shortcuts/Shortcut.cs
  100. 5 4
      PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs

+ 3 - 0
.github/FUNDING.yml

@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+open_collective: pixieditor

+ 62 - 0
Custom.ruleset

@@ -1,14 +1,76 @@
 <?xml version="1.0" encoding="utf-8"?>
 <RuleSet Name="Name" Description="Description" ToolsVersion="16.0">
+  <Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.Features" RuleNamespace="Microsoft.CodeAnalysis.CSharp.Features">
+    <Rule Id="IDE0090" Action="None" />
+  </Rules>
   <Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
     <Rule Id="CA1303" Action="None" />
   </Rules>
   <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
     <Rule Id="SA0001" Action="None" />
+    <Rule Id="SA1000" Action="None" />
+    <Rule Id="SA1005" Action="None" />
+    <Rule Id="SA1007" Action="None" />
+    <Rule Id="SA1028" Action="None" />
     <Rule Id="SA1101" Action="None" />
+    <Rule Id="SA1110" Action="None" />
+    <Rule Id="SA1111" Action="None" />
+    <Rule Id="SA1112" Action="None" />
+    <Rule Id="SA1117" Action="None" />
+    <Rule Id="SA1119" Action="None" />
+    <Rule Id="SA1122" Action="None" />
+    <Rule Id="SA1124" Action="None" />
+    <Rule Id="SA1128" Action="None" />
+    <Rule Id="SA1130" Action="None" />
+    <Rule Id="SA1132" Action="None" />
+    <Rule Id="SA1135" Action="None" />
+    <Rule Id="SA1136" Action="None" />
+    <Rule Id="SA1139" Action="None" />
+    <Rule Id="SA1200" Action="None" />
     <Rule Id="SA1201" Action="None" />
+    <Rule Id="SA1207" Action="None" />
+    <Rule Id="SA1208" Action="None" />
+    <Rule Id="SA1209" Action="None" />
+    <Rule Id="SA1210" Action="None" />
+    <Rule Id="SA1211" Action="None" />
+    <Rule Id="SA1216" Action="None" />
+    <Rule Id="SA1217" Action="None" />
+    <Rule Id="SA1303" Action="None" />
+    <Rule Id="SA1304" Action="None" />
+    <Rule Id="SA1307" Action="None" />
+    <Rule Id="SA1309" Action="None" />
     <Rule Id="SA1310" Action="None" />
+    <Rule Id="SA1311" Action="None" />
+    <Rule Id="SA1400" Action="None" />
+    <Rule Id="SA1401" Action="None" />
+    <Rule Id="SA1405" Action="None" />
+    <Rule Id="SA1406" Action="None" />
+    <Rule Id="SA1407" Action="None" />
+    <Rule Id="SA1410" Action="None" />
+    <Rule Id="SA1411" Action="None" />
     <Rule Id="SA1413" Action="None" />
+    <Rule Id="SA1501" Action="None" />
+    <Rule Id="SA1502" Action="None" />
+    <Rule Id="SA1503" Action="None" />
+    <Rule Id="SA1505" Action="None" />
+    <Rule Id="SA1507" Action="None" />
+    <Rule Id="SA1508" Action="None" />
+    <Rule Id="SA1512" Action="None" />
+    <Rule Id="SA1513" Action="None" />
+    <Rule Id="SA1515" Action="None" />
+    <Rule Id="SA1516" Action="None" />
+    <Rule Id="SA1518" Action="None" />
+    <Rule Id="SA1600" Action="None" />
+    <Rule Id="SA1601" Action="None" />
+    <Rule Id="SA1602" Action="None" />
+    <Rule Id="SA1604" Action="None" />
+    <Rule Id="SA1605" Action="None" />
+    <Rule Id="SA1606" Action="None" />
+    <Rule Id="SA1607" Action="None" />
+    <Rule Id="SA1629" Action="None" />
     <Rule Id="SA1633" Action="None" />
+    <Rule Id="SA1642" Action="None" />
+    <Rule Id="SA1643" Action="None" />
+    <Rule Id="SA1648" Action="None" />
   </Rules>
 </RuleSet>

BIN
PixiEditor.MSIX/Images/LargeTile.scale-100.png


BIN
PixiEditor.MSIX/Images/LargeTile.scale-125.png


BIN
PixiEditor.MSIX/Images/LargeTile.scale-150.png


BIN
PixiEditor.MSIX/Images/LargeTile.scale-200.png


BIN
PixiEditor.MSIX/Images/LargeTile.scale-400.png


BIN
PixiEditor.MSIX/Images/LockScreenLogo.scale-200.png


BIN
PixiEditor.MSIX/Images/SmallTile.scale-100.png


BIN
PixiEditor.MSIX/Images/SmallTile.scale-125.png


BIN
PixiEditor.MSIX/Images/SmallTile.scale-150.png


BIN
PixiEditor.MSIX/Images/SmallTile.scale-200.png


BIN
PixiEditor.MSIX/Images/SmallTile.scale-400.png


BIN
PixiEditor.MSIX/Images/SplashScreen.scale-100.png


BIN
PixiEditor.MSIX/Images/SplashScreen.scale-125.png


BIN
PixiEditor.MSIX/Images/SplashScreen.scale-150.png


BIN
PixiEditor.MSIX/Images/SplashScreen.scale-200.png


BIN
PixiEditor.MSIX/Images/SplashScreen.scale-400.png


BIN
PixiEditor.MSIX/Images/Square150x150Logo.scale-100.png


BIN
PixiEditor.MSIX/Images/Square150x150Logo.scale-125.png


BIN
PixiEditor.MSIX/Images/Square150x150Logo.scale-150.png


BIN
PixiEditor.MSIX/Images/Square150x150Logo.scale-200.png


BIN
PixiEditor.MSIX/Images/Square150x150Logo.scale-400.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-16.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-24.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-256.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-32.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-lightunplated_targetsize-48.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-unplated_targetsize-16.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-unplated_targetsize-256.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-unplated_targetsize-32.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.altform-unplated_targetsize-48.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.scale-100.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.scale-125.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.scale-150.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.scale-200.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.scale-400.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.targetsize-16.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.targetsize-24.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.targetsize-24_altform-unplated.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.targetsize-256.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.targetsize-32.png


BIN
PixiEditor.MSIX/Images/Square44x44Logo.targetsize-48.png


BIN
PixiEditor.MSIX/Images/StoreLogo.backup.png


BIN
PixiEditor.MSIX/Images/StoreLogo.scale-100.png


BIN
PixiEditor.MSIX/Images/StoreLogo.scale-125.png


BIN
PixiEditor.MSIX/Images/StoreLogo.scale-150.png


BIN
PixiEditor.MSIX/Images/StoreLogo.scale-200.png


BIN
PixiEditor.MSIX/Images/StoreLogo.scale-400.png


BIN
PixiEditor.MSIX/Images/Wide310x150Logo.scale-100.png


BIN
PixiEditor.MSIX/Images/Wide310x150Logo.scale-125.png


BIN
PixiEditor.MSIX/Images/Wide310x150Logo.scale-150.png


BIN
PixiEditor.MSIX/Images/Wide310x150Logo.scale-200.png


BIN
PixiEditor.MSIX/Images/Wide310x150Logo.scale-400.png


+ 66 - 0
PixiEditor.MSIX/Package.appxmanifest

@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<Package
+  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
+  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
+  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
+  IgnorableNamespaces="uap rescap">
+
+  <Identity
+    Name="6018c504-49c2-4830-ac0f-fab30b922158"
+    Publisher="CN=PixiEditor"
+    Version="0.2.0.0" />
+
+  <Properties>
+    <DisplayName>PixiEditor</DisplayName>
+    <PublisherDisplayName>PixiEditor</PublisherDisplayName>
+    <Logo>Images\StoreLogo.png</Logo>
+  </Properties>
+
+  <Dependencies>
+    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
+    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
+  </Dependencies>
+
+  <Resources>
+    <Resource Language="x-generate"/>
+  </Resources>
+
+  <Applications>
+    <Application Id="App"
+      Executable="$targetnametoken$.exe"
+      EntryPoint="$targetentrypoint$">
+      <uap:VisualElements
+        DisplayName="PixiEditor"
+        Description="PixiEditor is pixel-art editing software. Create beautiful sprites for your games, animations (coming soon!), and edit images. All packed in an eye-friendly dark theme."
+        BackgroundColor="transparent"
+        Square150x150Logo="Images\Square150x150Logo.png"
+        Square44x44Logo="Images\Square44x44Logo.png">
+        <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png"  Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png" ShortName="PixiEditor">
+          <uap:ShowNameOnTiles>
+            <uap:ShowOn Tile="square150x150Logo"/>
+            <uap:ShowOn Tile="wide310x150Logo"/>
+            <uap:ShowOn Tile="square310x310Logo"/>
+          </uap:ShowNameOnTiles>
+        </uap:DefaultTile >
+        <uap:SplashScreen Image="Images\SplashScreen.png" />
+      </uap:VisualElements>
+      <Extensions>
+        <uap:Extension Category="windows.fileTypeAssociation">
+          <uap:FileTypeAssociation Name="pixieditor.pixifile">
+            <uap:SupportedFileTypes>
+              <uap:FileType>.pixi</uap:FileType>
+            </uap:SupportedFileTypes>
+            <uap:DisplayName>PixiEditor</uap:DisplayName>
+            <uap:InfoTip>A file used to save art made in PixiEditor</uap:InfoTip>
+            <uap:EditFlags OpenIsSafe="true"/>
+          </uap:FileTypeAssociation>
+        </uap:Extension>
+      </Extensions>
+    </Application>
+  </Applications>
+
+  <Capabilities>
+    <rescap:Capability Name="runFullTrust" />
+  </Capabilities>
+</Package>

+ 129 - 0
PixiEditor.MSIX/PixiEditor.MSIX.wapproj

@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'">
+    <VisualStudioVersion>15.0</VisualStudioVersion>
+  </PropertyGroup>
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x86">
+      <Configuration>Debug</Configuration>
+      <Platform>x86</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x86">
+      <Configuration>Release</Configuration>
+      <Platform>x86</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|AnyCPU">
+      <Configuration>Debug</Configuration>
+      <Platform>AnyCPU</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|AnyCPU">
+      <Configuration>Release</Configuration>
+      <Platform>AnyCPU</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup>
+    <WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
+  </PropertyGroup>
+  <Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
+  <PropertyGroup>
+    <ProjectGuid>1f97f972-f9e8-4f35-a8b5-3f71408d2230</ProjectGuid>
+    <TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
+    <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
+    <DefaultLanguage>en-US</DefaultLanguage>
+    <AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
+    <EntryPointProjectUniqueName>..\PixiEditor\PixiEditor.csproj</EntryPointProjectUniqueName>
+    <PackageCertificateThumbprint>4C83B3D55F197ED681F813F8BEB48ACDED28FD6F</PackageCertificateThumbprint>
+    <PackageCertificateKeyFile>PixiEditor.MSIX_TemporaryKey.pfx</PackageCertificateKeyFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <AppxManifest Include="Package.appxmanifest">
+      <SubType>Designer</SubType>
+    </AppxManifest>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Images\LargeTile.scale-100.png" />
+    <Content Include="Images\LargeTile.scale-125.png" />
+    <Content Include="Images\LargeTile.scale-150.png" />
+    <Content Include="Images\LargeTile.scale-200.png" />
+    <Content Include="Images\LargeTile.scale-400.png" />
+    <Content Include="Images\SmallTile.scale-100.png" />
+    <Content Include="Images\SmallTile.scale-125.png" />
+    <Content Include="Images\SmallTile.scale-150.png" />
+    <Content Include="Images\SmallTile.scale-200.png" />
+    <Content Include="Images\SmallTile.scale-400.png" />
+    <Content Include="Images\SplashScreen.scale-100.png" />
+    <Content Include="Images\SplashScreen.scale-125.png" />
+    <Content Include="Images\SplashScreen.scale-150.png" />
+    <Content Include="Images\SplashScreen.scale-200.png" />
+    <Content Include="Images\LockScreenLogo.scale-200.png" />
+    <Content Include="Images\SplashScreen.scale-400.png" />
+    <Content Include="Images\Square150x150Logo.scale-100.png" />
+    <Content Include="Images\Square150x150Logo.scale-125.png" />
+    <Content Include="Images\Square150x150Logo.scale-150.png" />
+    <Content Include="Images\Square150x150Logo.scale-200.png" />
+    <Content Include="Images\Square150x150Logo.scale-400.png" />
+    <Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
+    <Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
+    <Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
+    <Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
+    <Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
+    <Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
+    <Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
+    <Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
+    <Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
+    <Content Include="Images\Square44x44Logo.scale-100.png" />
+    <Content Include="Images\Square44x44Logo.scale-125.png" />
+    <Content Include="Images\Square44x44Logo.scale-150.png" />
+    <Content Include="Images\Square44x44Logo.scale-200.png" />
+    <Content Include="Images\Square44x44Logo.scale-400.png" />
+    <Content Include="Images\Square44x44Logo.targetsize-16.png" />
+    <Content Include="Images\Square44x44Logo.targetsize-24.png" />
+    <Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
+    <Content Include="Images\Square44x44Logo.targetsize-256.png" />
+    <Content Include="Images\Square44x44Logo.targetsize-32.png" />
+    <Content Include="Images\Square44x44Logo.targetsize-48.png" />
+    <Content Include="Images\StoreLogo.scale-100.png" />
+    <Content Include="Images\StoreLogo.scale-125.png" />
+    <Content Include="Images\StoreLogo.scale-150.png" />
+    <Content Include="Images\StoreLogo.scale-200.png" />
+    <Content Include="Images\StoreLogo.scale-400.png" />
+    <Content Include="Images\Wide310x150Logo.scale-100.png" />
+    <Content Include="Images\Wide310x150Logo.scale-125.png" />
+    <Content Include="Images\Wide310x150Logo.scale-150.png" />
+    <Content Include="Images\Wide310x150Logo.scale-200.png" />
+    <Content Include="Images\Wide310x150Logo.scale-400.png" />
+  </ItemGroup>
+  <Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.19041.1" PrivateAssets="all" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\PixiEditor\PixiEditor.csproj">
+      <SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
+    </ProjectReference>
+  </ItemGroup>
+</Project>

+ 233 - 9
PixiEditor.sln

@@ -3,43 +3,267 @@ 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}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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
+	ProjectSection(SolutionItems) = preProject
+		Custom.ruleset = Custom.ruleset
+		Directory.Build.props = Directory.Build.props
+		stylecop.json = stylecop.json
+	EndProjectSection
+EndProject
+Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "PixiEditor.MSIX", "PixiEditor.MSIX\PixiEditor.MSIX.wapproj", "{1F97F972-F9E8-4F35-A8B5-3F71408D2230}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
+		Debug|ARM = Debug|ARM
+		Debug|ARM64 = Debug|ARM64
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		MSIX Debug|Any CPU = MSIX Debug|Any CPU
+		MSIX Debug|ARM = MSIX Debug|ARM
+		MSIX Debug|ARM64 = MSIX Debug|ARM64
+		MSIX Debug|x64 = MSIX Debug|x64
+		MSIX Debug|x86 = MSIX Debug|x86
+		MSIX|Any CPU = MSIX|Any CPU
+		MSIX|ARM = MSIX|ARM
+		MSIX|ARM64 = MSIX|ARM64
+		MSIX|x64 = MSIX|x64
+		MSIX|x86 = MSIX|x86
 		Release|Any CPU = Release|Any CPU
+		Release|ARM = Release|ARM
+		Release|ARM64 = Release|ARM64
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
 	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}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|ARM.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x64.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x86.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|Any CPU.ActiveCfg = MSIX Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|Any CPU.Build.0 = MSIX Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|ARM.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|ARM.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|ARM64.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|x86.ActiveCfg = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|x86.Build.0 = Debug|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|Any CPU.ActiveCfg = MSIX|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|Any CPU.Build.0 = MSIX|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|ARM.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|ARM.Build.0 = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|ARM64.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|ARM64.Build.0 = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|x64.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|x64.Build.0 = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|x86.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX|x86.Build.0 = Release|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
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|ARM.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|ARM.Build.0 = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|ARM64.Build.0 = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|x64.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|x64.Build.0 = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|x86.ActiveCfg = Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|x86.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}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|ARM.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|x64.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|x86.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|ARM.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|ARM.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|ARM64.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|x86.ActiveCfg = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|x86.Build.0 = Debug|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|Any CPU.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|Any CPU.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|ARM.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|ARM.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|ARM64.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|ARM64.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|x64.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|x64.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|x86.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX|x86.Build.0 = Release|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
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|ARM.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|ARM.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|ARM64.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x64.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x64.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x86.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x86.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}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|ARM.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x64.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x86.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|ARM.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|ARM.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|ARM64.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|x86.ActiveCfg = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|x86.Build.0 = Debug|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|Any CPU.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|Any CPU.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|ARM.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|ARM.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|ARM64.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|ARM64.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|x64.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|x64.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|x86.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX|x86.Build.0 = Release|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
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|ARM.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|ARM.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|ARM64.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x64.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x64.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x86.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x86.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}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|ARM.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|x64.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|x86.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|ARM.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|ARM.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|ARM64.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|x86.ActiveCfg = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|x86.Build.0 = Debug|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|Any CPU.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|Any CPU.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|ARM.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|ARM.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|ARM64.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|ARM64.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|x64.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|x64.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|x86.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX|x86.Build.0 = Release|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
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|ARM.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|ARM.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|ARM64.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|x64.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|x64.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|x86.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|x86.Build.0 = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|ARM.ActiveCfg = Debug|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|ARM.Build.0 = Debug|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|ARM.Deploy.0 = Debug|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|ARM64.ActiveCfg = Debug|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|ARM64.Build.0 = Debug|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|ARM64.Deploy.0 = Debug|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.ActiveCfg = Debug|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.Build.0 = Debug|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x64.Deploy.0 = Debug|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x86.ActiveCfg = Debug|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x86.Build.0 = Debug|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x86.Deploy.0 = Debug|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|Any CPU.Deploy.0 = Debug|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|ARM.ActiveCfg = Debug|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|ARM.Build.0 = Debug|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|ARM.Deploy.0 = Debug|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|ARM64.ActiveCfg = Debug|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|ARM64.Build.0 = Debug|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|ARM64.Deploy.0 = Debug|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|x64.ActiveCfg = Debug|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|x64.Build.0 = Debug|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|x64.Deploy.0 = Debug|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|x86.ActiveCfg = Debug|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|x86.Build.0 = Debug|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|x86.Deploy.0 = Debug|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|Any CPU.ActiveCfg = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|Any CPU.Build.0 = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|Any CPU.Deploy.0 = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|ARM.ActiveCfg = Release|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|ARM.Build.0 = Release|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|ARM.Deploy.0 = Release|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|ARM64.ActiveCfg = Release|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|ARM64.Build.0 = Release|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|ARM64.Deploy.0 = Release|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|x64.ActiveCfg = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|x64.Build.0 = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|x64.Deploy.0 = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|x86.ActiveCfg = Release|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|x86.Build.0 = Release|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX|x86.Deploy.0 = Release|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|Any CPU.Deploy.0 = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|ARM.ActiveCfg = Release|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|ARM.Build.0 = Release|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|ARM.Deploy.0 = Release|ARM
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|ARM64.ActiveCfg = Release|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|ARM64.Build.0 = Release|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|ARM64.Deploy.0 = Release|ARM64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|x64.ActiveCfg = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|x64.Build.0 = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|x64.Deploy.0 = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|x86.ActiveCfg = Release|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|x86.Build.0 = Release|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|x86.Deploy.0 = Release|x86
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 7 - 0
PixiEditor/App.xaml

@@ -16,6 +16,13 @@
                 <ResourceDictionary Source="Styles/ImageCheckBoxStyle.xaml" />
                 <ResourceDictionary Source="Styles/DarkCheckboxStyle.xaml" />
                 <ResourceDictionary Source="Styles/LabelStyles.xaml" />
+                <ResourceDictionary Source="Styles/AvalonDock/DarkBrushes.xaml" />
+                <ResourceDictionary Source="Styles/AvalonDock/Themes/Menu/DarkBrushes.xaml" />
+                <ResourceDictionary Source="Styles/AvalonDock/Themes/OverlayButtons.xaml" />
+                <ResourceDictionary Source="Styles/AvalonDock/Themes/Menu/MenuItem.xaml" />
+                <ResourceDictionary Source="Styles/AvalonDock/Themes/Icons/IconGeometry.xaml" />
+                <ResourceDictionary Source="Styles/AvalonDock/Themes/Generic.xaml" />
+                <ResourceDictionary Source="Styles/AvalonDock/PixiEditorDockTheme.xaml" />
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </Application.Resources>

+ 1 - 6
PixiEditor/Exceptions/CorruptedFileException.cs

@@ -1,12 +1,7 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace PixiEditor.Exceptions
 {
-
     [Serializable]
     public class CorruptedFileException : Exception
     {
@@ -32,4 +27,4 @@ namespace PixiEditor.Exceptions
         {
         }
     }
-}
+}

+ 3 - 6
PixiEditor/Helpers/AssemblyHelper.cs

@@ -8,11 +8,8 @@ namespace PixiEditor.Helpers
 {
     public static class AssemblyHelper
     {
-        public static string GetCurrentAssemblyVersion()
-        {
-            var assembly = Assembly.GetExecutingAssembly();
-            FileVersionInfo info = FileVersionInfo.GetVersionInfo(assembly.Location);
-            return info.FileVersion;
-        }
+        public static Version GetCurrentAssemblyVersion() => Assembly.GetExecutingAssembly().GetName().Version;
+
+        public static string GetCurrentAssemblyVersion(Func<Version, string> toString) => toString(GetCurrentAssemblyVersion());
     }
 }

+ 4 - 5
PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs

@@ -1,5 +1,4 @@
 using System.Windows;
-using System.Windows.Input;
 using System.Windows.Interactivity;
 
 namespace PixiEditor.Helpers.Behaviours
@@ -12,14 +11,14 @@ namespace PixiEditor.Helpers.Behaviours
             base.OnAttached();
         }
 
-        private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        protected override void OnDetaching()
         {
-            AssociatedObject.Focus();
+            AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
         }
 
-        protected override void OnDetaching()
+        private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
         {
-            AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
+            AssociatedObject.Focus();
         }
     }
 }

+ 38 - 0
PixiEditor/Helpers/Behaviours/GlobalShortcutFocusBehavior.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Interactivity;
+using PixiEditor.Models.Controllers.Shortcuts;
+
+namespace PixiEditor.Helpers.Behaviours
+{
+    public class GlobalShortcutFocusBehavior : Behavior<FrameworkElement>
+    {
+        protected override void OnAttached()
+        {
+            base.OnAttached();
+            AssociatedObject.GotKeyboardFocus += AssociatedObject_GotKeyboardFocus;
+            AssociatedObject.LostKeyboardFocus += AssociatedObject_LostKeyboardFocus;
+        }
+
+        protected override void OnDetaching()
+        {
+            base.OnDetaching();
+            AssociatedObject.GotKeyboardFocus -= AssociatedObject_GotKeyboardFocus;
+            AssociatedObject.LostKeyboardFocus -= AssociatedObject_LostKeyboardFocus;
+        }
+
+        private void AssociatedObject_LostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
+        {
+            ShortcutController.BlockShortcutExecution = false;
+        }
+
+        private void AssociatedObject_GotKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
+        {
+            ShortcutController.BlockShortcutExecution = true;
+        }
+    }
+}

+ 62 - 62
PixiEditor/Helpers/Behaviours/MouseBehaviour.cs → PixiEditor/Helpers/Behaviours/MouseBehavior.cs

@@ -1,63 +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;
-        }
-
-        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;
-        }
-    }
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Interactivity;
+
+namespace PixiEditor.Helpers.Behaviours
+{
+    public class MouseBehavior : Behavior<FrameworkElement>
+    {
+        public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
+            "MouseY", typeof(double), typeof(MouseBehavior), new PropertyMetadata(default(double)));
+
+        public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
+            "MouseX", typeof(double), typeof(MouseBehavior), 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(MouseBehavior),
+                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;
+        }
+    }
 }

+ 1 - 0
PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -3,6 +3,7 @@ using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Interactivity;
+using PixiEditor.Models.Controllers.Shortcuts;
 
 namespace PixiEditor.Helpers.Behaviours
 {

+ 24 - 0
PixiEditor/Helpers/Converters/BoolToBrushConverter.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class BoolToBrushConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            BrushTuple tuple = (BrushTuple)parameter;
+            return (bool)value ? tuple.FirstBrush : tuple.SecondBrush;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 53 - 0
PixiEditor/Helpers/Converters/BrushTuple.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class BrushTuple : NotifyableObject, ITuple
+    {
+        public object this[int index]
+        {
+            get
+            {
+                return index switch
+                {
+                    0 => FirstBrush,
+                    1 => SecondBrush,
+                    _ => throw new IndexOutOfRangeException("Index was out of range")
+                };
+            }
+        }
+
+        private Brush item1;
+
+        public Brush FirstBrush
+        {
+            get => item1;
+            set
+            {
+                item1 = value;
+                RaisePropertyChanged(nameof(FirstBrush));
+            }
+        }
+
+        private Brush item2;
+
+        public Brush SecondBrush
+        {
+            get => item2;
+            set
+            {
+                item2 = value;
+                RaisePropertyChanged(nameof(SecondBrush));
+            }
+        }
+
+        public int Length => 2;
+    }
+}

+ 1 - 1
PixiEditor/Helpers/Converters/DoubleToIntConverter.cs

@@ -22,4 +22,4 @@ namespace PixiEditor.Helpers.Converters
             throw new NotImplementedException();
         }
     }
-}
+}

+ 26 - 0
PixiEditor/Helpers/Converters/EmptyStringToVisibiltyConverter.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    class EmptyStringToVisibiltyConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            string s = (string)value;
+
+            return !string.IsNullOrEmpty(s) ? Visibility.Visible : Visibility.Collapsed;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 44 - 0
PixiEditor/Helpers/Converters/FileExtensionToImageSourceConverter.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class FileExtensionToImageSourceConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            string extension = (string)value;
+
+            if (extension == ".pixi")
+            {
+                return Join("PixiFile.png", parameter);
+            }
+            else if (extension == ".png")
+            {
+                return Join("PngFile.png", parameter);
+            }
+            else if (extension == ".jpg" || extension == ".jpeg")
+            {
+                return Join("JpgFile.png", parameter);
+            }
+
+            return Join("UnknownFile.png", parameter);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+
+        private string Join(string path, object parameter)
+        {
+            return Path.Join((string)parameter, "Images", path);
+        }
+    }
+}

+ 19 - 0
PixiEditor/Helpers/Converters/FloorConverter.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    class FloorConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return Math.Floor((double)value);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value;
+        }
+    }
+}

+ 20 - 0
PixiEditor/Helpers/Converters/IntToPickerTypeConverter.cs

@@ -0,0 +1,20 @@
+using ColorPicker.Models;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class IntToPickerTypeConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return (PickerType)value;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return (int)value;
+        }
+    }
+}

+ 24 - 26
PixiEditor/Helpers/Converters/BoolToColorConverter.cs → PixiEditor/Helpers/Converters/IntToViewportRectConverter.cs

@@ -1,27 +1,25 @@
-using System;
-using System.Globalization;
-using System.Windows.Data;
-
-namespace PixiEditor.Helpers.Converters
-{
-    public class BoolToColorConverter : IValueConverter
-    {
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            return value?.ToString() == "Transparent";
-        }
-
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            if (value is bool boolean)
-            {
-                if (boolean == false)
-                {
-                    return "Transparent";
-                }
-            }
-
-            return "#638DCA";
-        }
-    }
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class IntToViewportRectConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (parameter is string and "vertical")
+            {
+                return new Rect(0, 0, 1d / (int)value, 1d);
+            }
+
+            return new Rect(0, 0, 1d, 1d / (int)value);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
 }

+ 35 - 0
PixiEditor/Helpers/Converters/KeyToStringConverter.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+using System.Windows.Input;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class KeyToStringConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is Key key)
+            {
+                return InputKeyHelpers.GetCharFromKey(key);
+            }
+            else if (value is ModifierKeys)
+            {
+                return value.ToString();
+            }
+            else
+            {
+                return string.Empty;
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 26 - 0
PixiEditor/Helpers/Converters/NotNullToBoolConverter.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    [ValueConversion(typeof(object), typeof(bool))]
+    public class NotNullToBoolConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            bool result = value != null;
+            if (parameter != null)
+            {
+                return !result;
+            }
+
+            return result;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value;
+        }
+    }
+}

+ 26 - 0
PixiEditor/Helpers/Converters/ThresholdVisibilityConverter.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    class ThresholdVisibilityConverter : IValueConverter
+    {
+        public double Threshold { get; set; } = 100;
+        public bool CheckIfLess { get; set; } = false;
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (CheckIfLess)
+                return (double)value < Threshold ? Visibility.Visible : Visibility.Hidden;
+            else
+                return (double)value >= Threshold ? Visibility.Visible : Visibility.Hidden;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 59 - 0
PixiEditor/Helpers/CrashHelper.cs

@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Helpers
+{
+    public static class CrashHelper
+    {
+        public static void SaveCrashInfo(Exception e)
+        {
+            StringBuilder builder = new System.Text.StringBuilder();
+            DateTime currentTime = DateTime.Now;
+
+            builder
+                .Append($"PixiEditor crashed on {currentTime:yyyy.MM.dd} at {currentTime:HH:mm:ss}\n\n")
+                .Append("-------Crash message-------\n")
+                .Append(e.GetType().ToString())
+                .Append(": ")
+                .Append(e.Message);
+            {
+                var innerException = e.InnerException;
+                while (innerException != null)
+                {
+                    builder
+                        .Append("\n-----Inner exception-----\n")
+                        .Append(innerException.GetType().ToString())
+                        .Append(": ")
+                        .Append(innerException.Message);
+                    innerException = innerException.InnerException;
+                }
+            }
+
+            builder
+                .Append("\n\n-------Stack trace-------\n")
+                .Append(e.StackTrace);
+            {
+                var innerException = e.InnerException;
+                while (innerException != null)
+                {
+                    builder
+                        .Append("\n-----Inner exception-----\n")
+                        .Append(innerException.StackTrace);
+                    innerException = innerException.InnerException;
+                }
+            }
+
+            string filename = $"crash-{currentTime:yyyy-MM-dd_HH-mm-ss_fff}.txt";
+            string path = Path.Combine(
+                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+                "PixiEditor",
+                "crash_logs");
+            Directory.CreateDirectory(path);
+            File.WriteAllText(Path.Combine(path, filename), builder.ToString());
+        }
+    }
+}

+ 17 - 0
PixiEditor/Helpers/Extensions/EnumHelpers.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class EnumHelpers
+    {
+        public static IEnumerable<T> GetFlags<T>(this T e)
+               where T : Enum
+        {
+            return Enum.GetValues(e.GetType()).Cast<T>().Where(x => e.HasFlag(x));
+        }
+    }
+}

+ 16 - 0
PixiEditor/Helpers/Extensions/ObservableCollectionEx.cs

@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class ObservableCollectionEx
+    {
+        public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
+        {
+            foreach (var item in items)
+            {
+                collection.Add(item);
+            }
+        }
+    }
+}

+ 8 - 1
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -20,6 +20,11 @@ namespace PixiEditor.Helpers.Extensions
                     Color.FromArgb(x.Item1, x.Item2, x.Item3, x.Item4)))
             };
 
+            if (document.Layers.Count > 0)
+            {
+                document.SetMainActiveLayer(0);
+            }
+
             return document;
         }
 
@@ -34,7 +39,9 @@ namespace PixiEditor.Helpers.Extensions
                     {
                         IsVisible = serLayer.IsVisible,
                         Offset = new Thickness(serLayer.OffsetX, serLayer.OffsetY, 0, 0),
-                        Opacity = serLayer.Opacity
+                        Opacity = serLayer.Opacity,
+                        MaxHeight = serializableDocument.Height,
+                        MaxWidth = serializableDocument.Width,
                     };
                 layers.Add(layer);
             }

+ 27 - 0
PixiEditor/Helpers/Extensions/StringHelpers.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class StringHelpers
+    {
+        public static string AddSpacesBeforeUppercaseLetters(this string text)
+        {
+            if (string.IsNullOrWhiteSpace(text))
+                return "";
+
+            StringBuilder newText = new StringBuilder(text.Length * 2);
+            newText.Append(text[0]);
+            for (int i = 1; i < text.Length; i++)
+            {
+                if (char.IsUpper(text[i]) && text[i - 1] != ' ')
+                    newText.Append(' ');
+                newText.Append(text[i]);
+            }
+            return newText.ToString();
+        }
+    }
+}

+ 15 - 0
PixiEditor/Helpers/Extensions/ToolbarHelpers.cs

@@ -0,0 +1,15 @@
+using System;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class ToolbarHelpers
+    {
+        public static EnumSetting<TEnum> GetEnumSetting<TEnum>(this Toolbar toolbar, string name)
+            where TEnum : struct, Enum
+        {
+            return toolbar.GetSetting<EnumSetting<TEnum>>(name);
+        }
+    }
+}

+ 82 - 0
PixiEditor/Helpers/InputKeyHelpers.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace PixiEditor.Helpers
+{
+    public static class InputKeyHelpers
+    {
+        public static string GetCharFromKey(Key key)
+        {
+            int virtualKey = KeyInterop.VirtualKeyFromKey(key);
+            byte[] keyboardState = new byte[256];
+            GetKeyboardState(keyboardState);
+
+            uint scanCode = MapVirtualKeyW((uint)virtualKey, MapType.MAPVK_VK_TO_VSC);
+            StringBuilder stringBuilder = new (3);
+
+            int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, stringBuilder, stringBuilder.Capacity, 0);
+
+            switch (result)
+            {
+                case 0:
+                    {
+                        return key.ToString();
+                    }
+
+                case -1:
+                    {
+                        return stringBuilder.ToString().ToUpper();
+                    }
+
+                default:
+                    {
+                        return stringBuilder[result - 1].ToString().ToUpper();
+                    }
+            }
+        }
+
+        private enum MapType : uint
+        {
+            /// <summary>
+            /// The uCode parameter is a virtual-key code and is translated into a scan code. If it is a virtual-key code that does not distinguish between left- and right-hand keys, the left-hand scan code is returned. If there is no translation, the function returns 0.
+            /// </summary>
+            MAPVK_VK_TO_VSC = 0x0,
+
+            /// <summary>
+            /// The uCode parameter is a scan code and is translated into a virtual-key code that does not distinguish between left- and right-hand keys. If there is no translation, the function returns 0.
+            /// </summary>
+            MAPVK_VSC_TO_VK = 0x1,
+
+            /// <summary>
+            /// The uCode parameter is a virtual-key code and is translated into an unshifted character value in the low order word of the return value. Dead keys (diacritics) are indicated by setting the top bit of the return value. If there is no translation, the function returns 0.
+            /// </summary>
+            MAPVK_VK_TO_CHAR = 0x2,
+
+            /// <summary>
+            /// The uCode parameter is a scan code and is translated into a virtual-key code that distinguishes between left- and right-hand keys. If there is no translation, the function returns 0.
+            /// </summary>
+            MAPVK_VSC_TO_VK_EX = 0x3,
+        }
+
+        [DllImport("user32.dll")]
+        private static extern int ToUnicode(
+            uint wVirtKey,
+            uint wScanCode,
+            byte[] lpKeyState,
+            [Out, MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 4)]
+            StringBuilder pwszBuff,
+            int cchBuff,
+            uint wFlags);
+
+        [DllImport("user32.dll")]
+        private static extern bool GetKeyboardState(byte[] lpKeyState);
+
+        [DllImport("user32.dll")]
+        private static extern uint MapVirtualKeyW(uint uCode, MapType uMapType);
+    }
+}

+ 43 - 0
PixiEditor/Helpers/SelectionHelpers.cs

@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Undo;
+
+namespace PixiEditor.Helpers
+{
+    public static class SelectionHelpers
+    {
+        public static void AddSelectionUndoStep(Document document, IEnumerable<Coordinates> oldPoints, SelectionType mode)
+        {
+#pragma warning disable SA1117 // Parameters should be on same line or separate lines. Justification: Making it readable
+            if (mode == SelectionType.New && document.ActiveSelection.SelectedPoints.Count != 0)
+            {
+                // Add empty selection as the old one get's fully deleted first
+                document.UndoManager.AddUndoChange(
+                    new Change(
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>(oldPoints) },
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>() }));
+                document.UndoManager.AddUndoChange(
+                    new Change(
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>() },
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>(document.ActiveSelection.SelectedPoints) }));
+            }
+            else
+            {
+                document.UndoManager.AddUndoChange(
+                    new Change(
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>(oldPoints) },
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>(document.ActiveSelection.SelectedPoints) }));
+#pragma warning restore SA1117 // Parameters should be on same line or separate lines
+            }
+        }
+
+        private static void SetSelectionProcess(object[] arguments)
+        {
+            Document document = (Document)arguments[0];
+
+            document.ActiveSelection.SetSelection((IEnumerable<Coordinates>)arguments[1], SelectionType.New);
+        }
+    }
+}

+ 13 - 0
PixiEditor/Helpers/StringExtensions.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Linq;
+
+namespace PixiEditor.Helpers
+{
+    public static class StringExtensions
+    {
+        public static string Reverse(this string s)
+        {
+            return new string(s.Reverse<char>().ToArray());
+        }
+    }
+}

+ 28 - 0
PixiEditor/Helpers/UI/DocumentsTemplateSelector.cs

@@ -0,0 +1,28 @@
+using System.Windows;
+using System.Windows.Controls;
+using AvalonDock.Layout;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Helpers.UI
+{
+    public class DocumentsTemplateSelector : DataTemplateSelector
+    {
+        public DocumentsTemplateSelector()
+        {
+
+        }
+
+        public DataTemplate DocumentsViewTemplate { get; set; }
+
+        public override DataTemplate SelectTemplate(object item, DependencyObject container)
+        {
+            if (item is Document)
+            {
+                return DocumentsViewTemplate;
+            }
+
+            return base.SelectTemplate(item, container);
+        }
+    }
+}

+ 20 - 0
PixiEditor/Helpers/UI/PanelsStyleSelector.cs

@@ -0,0 +1,20 @@
+using System.Windows;
+using System.Windows.Controls;
+using PixiEditor.Models.DataHolders;
+
+namespace PixiEditor.Helpers.UI
+{
+    public class PanelsStyleSelector : StyleSelector
+    {
+        public Style DocumentTabStyle { get; set; }
+
+        public override Style SelectStyle(object item, DependencyObject container)
+        {
+            if (item is Document)
+            {
+                return DocumentTabStyle;
+            }
+            return base.SelectStyle(item, container);
+        }
+    }
+}

BIN
PixiEditor/Images/JpgFile.png


BIN
PixiEditor/Images/PixiBotLogo.png


BIN
PixiEditor/Images/PixiFile.png


BIN
PixiEditor/Images/PixiParserLogo.png


BIN
PixiEditor/Images/PngFile.png


BIN
PixiEditor/Images/UnknownFile.png


+ 5 - 5
PixiEditor/Models/Controllers/BitmapChangedEventArgs.cs

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

+ 22 - 6
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Diagnostics;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
@@ -18,10 +19,12 @@ using PixiEditor.Models.Tools.ToolSettings.Settings;
 
 namespace PixiEditor.Models.Controllers
 {
+    [DebuggerDisplay("{Documents.Count} Document(s)")]
     public class BitmapManager : NotifyableObject
     {
         private Document activeDocument;
         private Tool selectedTool;
+        private Coordinates? startPosition = null;
 
         public BitmapManager()
         {
@@ -60,7 +63,7 @@ namespace PixiEditor.Models.Controllers
             : 1;
             set
             {
-                if (SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is var toolSize)
+                if (SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is SizeSetting toolSize)
                 {
                     toolSize.Value = value;
                     HighlightPixels(MousePositionConverter.CurrentCoordinates);
@@ -70,19 +73,23 @@ namespace PixiEditor.Models.Controllers
 
         public BitmapOperationsUtility BitmapOperations { get; set; }
 
-        public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
-
+        public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
+
+#nullable enable
         public Document ActiveDocument
         {
             get => activeDocument;
             set
             {
+                activeDocument?.UpdatePreviewImage();
+                Document? oldDoc = activeDocument;
                 activeDocument = value;
-                RaisePropertyChanged("ActiveDocument");
-                DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value));
+                RaisePropertyChanged(nameof(ActiveDocument));
+                DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value, oldDoc));
             }
         }
 
+#nullable disable
         public ObservableCollection<Document> Documents { get; set; } = new ObservableCollection<Document>();
 
         /// <summary>
@@ -110,6 +117,12 @@ namespace PixiEditor.Models.Controllers
         {
             if (SelectedTool.CanStartOutsideCanvas || clickedOnCanvas)
             {
+                if (startPosition == null)
+                {
+                    SelectedTool.OnStart(newPosition);
+                    startPosition = newPosition;
+                }
+
                 if (IsOperationTool(SelectedTool))
                 {
                     BitmapOperations.ExecuteTool(newPosition, MouseController.LastMouseMoveCoordinates.ToList(), (BitmapOperationTool)SelectedTool);
@@ -123,7 +136,7 @@ namespace PixiEditor.Models.Controllers
 
         public WriteableBitmap GetCombinedLayersBitmap()
         {
-            return BitmapUtils.CombineLayers(ActiveDocument.Layers.Where(x => x.IsVisible).ToArray(), ActiveDocument.Width, ActiveDocument.Height);
+            return BitmapUtils.CombineLayers(ActiveDocument.Width, ActiveDocument.Height, ActiveDocument.Layers.Where(x => x.IsVisible).ToArray());
         }
 
         /// <summary>
@@ -140,6 +153,7 @@ namespace PixiEditor.Models.Controllers
             {
                 ActiveDocument.PreviewLayer = null;
             }
+
             SelectedTool?.Toolbar.SaveToolbarSettings();
             SelectedTool = tool;
             SelectedTool.Toolbar.LoadSharedSettings();
@@ -189,6 +203,8 @@ namespace PixiEditor.Models.Controllers
             {
                 BitmapOperations.ApplyPreviewLayer();
             }
+
+            startPosition = null;
         }
 
         private void HighlightPixels(Coordinates newPosition)

+ 262 - 197
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -1,198 +1,263 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
-
-namespace PixiEditor.Models.Controllers
-{
-    public class BitmapOperationsUtility
-    {
-        private LayerChange[] lastModifiedLayers;
-
-        private Coordinates lastMousePos;
-
-        public BitmapOperationsUtility(BitmapManager manager)
-        {
-            Manager = manager;
-        }
-
-        public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
-
-        public BitmapManager Manager { get; set; }
-
-        public void DeletePixels(Layer[] layers, Coordinates[] pixels)
-        {
-            if (Manager.ActiveDocument == null)
-            {
-                return;
-            }
-
-            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++)
-            {
-                int indexOfLayer = Manager.ActiveDocument.Layers.IndexOf(layers[i]);
-                old[i] = new LayerChange(
-                    BitmapPixelChanges.FromArrays(pixels, oldValues[layers[i]]), indexOfLayer);
-                newChange[i] = new LayerChange(changes, indexOfLayer);
-                layers[i].SetPixels(changes);
-            }
-
-            Manager.ActiveDocument.UndoManager.AddUndoChange(new Change("UndoChanges", old, newChange, "Deleted pixels"));
-        }
-
-        /// <summary>
-        ///     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>
-        public void ExecuteTool(Coordinates newPos, List<Coordinates> mouseMove, BitmapOperationTool tool)
-        {
-            if (Manager.ActiveDocument != null && tool != null)
-            {
-                if (Manager.ActiveDocument.Layers.Count == 0 || mouseMove.Count == 0)
-                {
-                    return;
-                }
-
-                mouseMove.Reverse();
-                UseTool(mouseMove, tool, Manager.PrimaryColor);
-
-                lastMousePos = newPos;
-            }
-        }
-
-        /// <summary>
-        ///     Applies pixels from preview layer to selected layer.
-        /// </summary>
-        public void ApplyPreviewLayer()
-        {
-            if (lastModifiedLayers == null)
-            {
-                return;
-            }
-
-            for (int i = 0; i < lastModifiedLayers.Length; i++)
-            {
-                Layer layer = Manager.ActiveDocument.Layers[lastModifiedLayers[i].LayerIndex];
-
-                BitmapPixelChanges oldValues = ApplyToLayer(layer, lastModifiedLayers[i]).PixelChanges;
-
-                BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(
-                    lastModifiedLayers[i].PixelChanges,
-                    oldValues,
-                    lastModifiedLayers[i].LayerIndex));
-                Manager.ActiveDocument.GeneratePreviewLayer();
-            }
-        }
-
-        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++)
-                {
-                    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));
-                }
-            }
-            else
-            {
-                UseToolOnPreviewLayer(mouseMoveCords);
-            }
-        }
-
-        private LayerChange ApplyToLayer(Layer layer, LayerChange change)
-        {
-            layer.DynamicResize(change.PixelChanges);
-
-            LayerChange oldPixelsValues = new LayerChange(
-                GetOldPixelsValues(change.PixelChanges.ChangedPixels.Keys.ToArray()),
-                change.LayerIndex);
-
-            layer.SetPixels(change.PixelChanges, false);
-            return oldPixelsValues;
-        }
-
-        private bool MouseCordsNotInLine(List<Coordinates> cords)
-        {
-            return cords[0].X == cords[^1].X || cords[0].Y == cords[^1].Y;
-        }
-
-        /// <summary>
-        ///     Extracts square from rectangle mouse drag, used to draw symmetric shapes.
-        /// </summary>
-        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;
-            }
-
-            mouseMoveCords[0] = new Coordinates(mouseMoveCords[^1].X + xLength, mouseMoveCords[^1].Y + yLength);
-            return mouseMoveCords;
-        }
-
-        private BitmapPixelChanges GetOldPixelsValues(Coordinates[] coordinates)
-        {
-            Dictionary<Coordinates, Color> values = new Dictionary<Coordinates, Color>();
-            using (Manager.ActiveLayer.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
-            {
-                Coordinates[] relativeCoords = Manager.ActiveLayer.ConvertToRelativeCoordinates(coordinates);
-                for (int i = 0; i < coordinates.Length; 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)
-            {
-                Manager.ActiveDocument.GeneratePreviewLayer();
-                modifiedLayers = ((BitmapOperationTool)Manager.SelectedTool).Use(
-                    Manager.ActiveDocument.ActiveLayer,
-                    mouseMove.ToArray(),
-                    Manager.PrimaryColor);
-                BitmapPixelChanges[] changes = modifiedLayers.Select(x => x.PixelChanges).ToArray();
-                Manager.ActiveDocument.PreviewLayer.SetPixels(BitmapPixelChanges.CombineOverride(changes));
-                lastModifiedLayers = modifiedLayers;
-            }
-        }
-    }
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using PixiEditor.Models.Undo;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class BitmapOperationsUtility
+    {
+        public List<LayerChange> PreviewLayerChanges => previewLayerChanges;
+
+        private List<LayerChange> previewLayerChanges;
+
+        private Coordinates lastMousePos;
+
+        public BitmapOperationsUtility(BitmapManager manager)
+        {
+            Manager = manager;
+        }
+
+        public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
+
+        public BitmapManager Manager { get; set; }
+
+        public void DeletePixels(Layer[] layers, Coordinates[] pixels)
+        {
+            if (Manager.ActiveDocument == null)
+            {
+                return;
+            }
+
+            BitmapPixelChanges changes = BitmapPixelChanges.FromSingleColoredArray(pixels, Color.FromArgb(0, 0, 0, 0));
+            Dictionary<Guid, 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++)
+            {
+                Guid guid = layers[i].LayerGuid;
+                old[i] = new LayerChange(
+                    BitmapPixelChanges.FromArrays(pixels, oldValues[layers[i].LayerGuid]), guid);
+                newChange[i] = new LayerChange(changes, guid);
+                layers[i].SetPixels(changes);
+            }
+
+            Manager.ActiveDocument.UndoManager.AddUndoChange(new Change("UndoChanges", old, newChange, "Deleted pixels"));
+        }
+
+        /// <summary>
+        ///     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>
+        public void ExecuteTool(Coordinates newPos, List<Coordinates> mouseMove, BitmapOperationTool tool)
+        {
+            if (Manager.ActiveDocument != null && tool != null)
+            {
+                if (Manager.ActiveDocument.Layers.Count == 0 || mouseMove.Count == 0)
+                {
+                    return;
+                }
+
+                mouseMove.Reverse();
+                UseTool(mouseMove, tool, Manager.PrimaryColor);
+
+                lastMousePos = newPos;
+            }
+        }
+
+        /// <summary>
+        ///     Applies pixels from preview layer to selected layer.
+        /// </summary>
+        public void ApplyPreviewLayer()
+        {
+            if (previewLayerChanges == null)
+            {
+                return;
+            }
+
+            Layer[] layers = new Layer[previewLayerChanges.Count];
+
+            for (int i = 0; i < layers.Length; i++)
+            {
+                layers[i] = Manager.ActiveDocument.Layers.First(x => x.LayerGuid == previewLayerChanges[i].LayerGuid);
+            }
+
+            if (layers.Length > 0)
+            {
+                IEnumerable<LayerChange> oldValues =
+                    ApplyToLayers(layers, previewLayerChanges.ToArray());
+
+                foreach (var oldValue in oldValues)
+                {
+                    var previewChanges = previewLayerChanges.First(x => x.LayerGuid == oldValue.LayerGuid);
+
+                    BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(
+                        previewChanges.PixelChanges,
+                        oldValue.PixelChanges,
+                        previewChanges.LayerGuid));
+                }
+
+                Manager.ActiveDocument.GeneratePreviewLayer();
+            }
+
+            previewLayerChanges = 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++)
+                {
+                    Layer layer = Manager.ActiveDocument.Layers.First(x => x.LayerGuid == modifiedLayers[i].LayerGuid);
+                    oldPixelsValues[i] = ApplyToLayer(layer, modifiedLayers[i]);
+
+                    BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(
+                        modifiedLayers[i].PixelChanges,
+                        oldPixelsValues[i].PixelChanges,
+                        modifiedLayers[i].LayerGuid));
+                }
+            }
+            else
+            {
+                UseToolOnPreviewLayer(mouseMoveCords, tool.ClearPreviewLayerOnEachIteration);
+            }
+        }
+
+        private LayerChange ApplyToLayer(Layer layer, LayerChange change)
+        {
+            return ApplyToLayers(new Layer[] { layer }, new LayerChange[] { change })[0];
+        }
+
+        private LayerChange[] ApplyToLayers(Layer[] layers, LayerChange[] changes)
+        {
+            LayerChange[] oldPixelValues = new LayerChange[changes.Length];
+            for (int i = 0; i < layers.Length; i++)
+            {
+                Layer layer = layers[i];
+                LayerChange change = changes.First(x => x.LayerGuid == layer.LayerGuid);
+                layer.DynamicResize(change.PixelChanges);
+
+                oldPixelValues[i] = new LayerChange(
+                GetOldPixelsValues(change.PixelChanges.ChangedPixels.Keys.ToArray()),
+                change.LayerGuid);
+            }
+
+            for (int i = 0; i < layers.Length; i++)
+            {
+                Layer layer = layers[i];
+                LayerChange change = changes.First(x => x.LayerGuid == layer.LayerGuid);
+                layer.SetPixels(change.PixelChanges, false);
+            }
+
+            return oldPixelValues;
+        }
+
+        private bool MouseCordsNotInLine(List<Coordinates> cords)
+        {
+            return cords[0].X == cords[^1].X || cords[0].Y == cords[^1].Y;
+        }
+
+        /// <summary>
+        ///     Extracts square from rectangle mouse drag, used to draw symmetric shapes.
+        /// </summary>
+        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;
+            }
+
+            mouseMoveCords[0] = new Coordinates(mouseMoveCords[^1].X + xLength, mouseMoveCords[^1].Y + yLength);
+            return mouseMoveCords;
+        }
+
+        private BitmapPixelChanges GetOldPixelsValues(Coordinates[] coordinates)
+        {
+            Dictionary<Coordinates, Color> values = new Dictionary<Coordinates, Color>();
+            using (Manager.ActiveLayer.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
+            {
+                Coordinates[] relativeCoords = Manager.ActiveLayer.ConvertToRelativeCoordinates(coordinates);
+                for (int i = 0; i < coordinates.Length; i++)
+                {
+                    values.Add(
+                        coordinates[i],
+                        Manager.ActiveLayer.GetPixel(relativeCoords[i].X, relativeCoords[i].Y));
+                }
+            }
+
+            return new BitmapPixelChanges(values);
+        }
+
+        private void UseToolOnPreviewLayer(List<Coordinates> mouseMove, bool clearPreviewLayer = true)
+        {
+            LayerChange[] modifiedLayers;
+            if (mouseMove.Count > 0 && mouseMove[0] != lastMousePos)
+            {
+                if (clearPreviewLayer || Manager.ActiveDocument.PreviewLayer == null)
+                {
+                    Manager.ActiveDocument.GeneratePreviewLayer();
+                }
+
+                modifiedLayers = ((BitmapOperationTool)Manager.SelectedTool).Use(
+                    Manager.ActiveDocument.ActiveLayer,
+                    mouseMove.ToArray(),
+                    Manager.PrimaryColor);
+
+                BitmapPixelChanges[] changes = modifiedLayers.Select(x => x.PixelChanges).ToArray();
+                if (changes.Length == 0)
+                {
+                    return;
+                }
+
+                Manager.ActiveDocument.PreviewLayer.SetPixels(BitmapPixelChanges.CombineOverride(changes));
+
+                if (clearPreviewLayer || previewLayerChanges == null)
+                {
+                    previewLayerChanges = new List<LayerChange>(modifiedLayers);
+                }
+                else
+                {
+                    InjectPreviewLayerChanges(modifiedLayers);
+                }
+            }
+        }
+
+        private void InjectPreviewLayerChanges(LayerChange[] modifiedLayers)
+        {
+            for (int i = 0; i < modifiedLayers.Length; i++)
+            {
+                var layer = previewLayerChanges.First(x => x.LayerGuid == modifiedLayers[i].LayerGuid);
+                layer.PixelChanges.ChangedPixels.AddRangeOverride(modifiedLayers[i].PixelChanges.ChangedPixels);
+                layer.PixelChanges = layer.PixelChanges.WithoutTransparentPixels();
+            }
+        }
+    }
 }

+ 29 - 29
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -1,12 +1,12 @@
-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.ImageManipulation;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using PixiEditor.Models.Undo;
 using PixiEditor.ViewModels;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.Controllers
 {
@@ -19,7 +19,7 @@ namespace PixiEditor.Models.Controllers
         public static void CopyToClipboard(Layer[] layers, Coordinates[] selection, int originalImageWidth, int originalImageHeight)
         {
             Clipboard.Clear();
-            WriteableBitmap combinedBitmaps = BitmapUtils.CombineLayers(layers, originalImageWidth, originalImageHeight);
+            WriteableBitmap combinedBitmaps = BitmapUtils.CombineLayers(originalImageWidth, originalImageHeight, layers);
             using (MemoryStream pngStream = new MemoryStream())
             {
                 DataObject data = new DataObject();
@@ -51,26 +51,6 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
-        private static void RemoveLayerProcess(object[] parameters)
-        {
-            if (parameters.Length == 0 || !(parameters[0] is int))
-            {
-                return;
-            }
-
-            ViewModelMain.Current.BitmapManager.ActiveDocument.RemoveLayer((int)parameters[0]);
-        }
-
-        private static void AddLayerProcess(object[] parameters)
-        {
-            if (parameters.Length == 0 || !(parameters[0] is WriteableBitmap))
-            {
-                return;
-            }
-
-            AddImageToLayers((WriteableBitmap)parameters[0]);
-        }
-
         /// <summary>
         ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
         /// </summary>
@@ -92,11 +72,11 @@ namespace PixiEditor.Models.Controllers
             }
             else if (dao.GetDataPresent(DataFormats.Dib))
             {
-                finalImage = new WriteableBitmap(Clipboard.GetImage() !);
+                finalImage = new WriteableBitmap(Clipboard.GetImage()!);
             }
             else if (dao.GetDataPresent(DataFormats.Bitmap))
             {
-                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource) !);
+                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource)!);
             }
 
             return finalImage;
@@ -127,5 +107,25 @@ namespace PixiEditor.Models.Controllers
         {
             ViewModelMain.Current.BitmapManager.ActiveDocument.AddNewLayer("Image", image);
         }
+
+        private static void RemoveLayerProcess(object[] parameters)
+        {
+            if (parameters.Length == 0 || !(parameters[0] is int))
+            {
+                return;
+            }
+
+            ViewModelMain.Current.BitmapManager.ActiveDocument.RemoveLayer((int)parameters[0]);
+        }
+
+        private static void AddLayerProcess(object[] parameters)
+        {
+            if (parameters.Length == 0 || !(parameters[0] is WriteableBitmap))
+            {
+                return;
+            }
+
+            AddImageToLayers((WriteableBitmap)parameters[0]);
+        }
     }
 }

+ 3 - 3
PixiEditor/Models/Controllers/LayersChangedEventArgs.cs

@@ -5,13 +5,13 @@ namespace PixiEditor.Models.Controllers
 {
     public class LayersChangedEventArgs : EventArgs
     {
-        public LayersChangedEventArgs(int layerAffected, LayerAction layerChangeType)
+        public LayersChangedEventArgs(Guid layerAffectedGuid, LayerAction layerChangeType)
         {
-            LayerAffected = layerAffected;
+            LayerAffectedGuid = layerAffectedGuid;
             LayerChangeType = layerChangeType;
         }
 
-        public int LayerAffected { get; set; }
+        public Guid LayerAffectedGuid { get; set; }
 
         public LayerAction LayerChangeType { get; set; }
     }

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

@@ -7,9 +7,9 @@ namespace PixiEditor.Models.Controllers
 {
     public class PixelChangesController
     {
-        private Dictionary<int, LayerChange> LastChanges { get; set; }
+        private Dictionary<Guid, LayerChange> LastChanges { get; set; }
 
-        private Dictionary<int, LayerChange> LastOldValues { get; set; }
+        private Dictionary<Guid, LayerChange> LastOldValues { get; set; }
 
         /// <summary>
         ///     Adds layer changes to controller.
@@ -22,10 +22,10 @@ namespace PixiEditor.Models.Controllers
             {
                 if (LastChanges == null)
                 {
-                    LastChanges = new Dictionary<int, LayerChange> { { changes.LayerIndex, changes } };
-                    LastOldValues = new Dictionary<int, LayerChange> { { oldValues.LayerIndex, oldValues } };
+                    LastChanges = new Dictionary<Guid, LayerChange> { { changes.LayerGuid, changes } };
+                    LastOldValues = new Dictionary<Guid, LayerChange> { { oldValues.LayerGuid, oldValues } };
                 }
-                else if (LastChanges.ContainsKey(changes.LayerIndex))
+                else if (LastChanges.ContainsKey(changes.LayerGuid))
                 {
                     AddToExistingLayerChange(changes, oldValues);
                 }
@@ -50,7 +50,7 @@ namespace PixiEditor.Models.Controllers
 
             Tuple<LayerChange, LayerChange>[] result = new Tuple<LayerChange, LayerChange>[LastChanges.Count];
             int i = 0;
-            foreach (KeyValuePair<int, LayerChange> change in LastChanges)
+            foreach (KeyValuePair<Guid, LayerChange> change in LastChanges)
             {
                 Dictionary<Position.Coordinates, System.Windows.Media.Color> pixelChanges =
                     change.Value.PixelChanges.ChangedPixels.ToDictionary(entry => entry.Key, entry => entry.Value);
@@ -71,33 +71,33 @@ namespace PixiEditor.Models.Controllers
 
         private void AddNewLayerChange(LayerChange changes, LayerChange oldValues)
         {
-            LastChanges[changes.LayerIndex] = changes;
-            LastOldValues[changes.LayerIndex] = oldValues;
+            LastChanges[changes.LayerGuid] = changes;
+            LastOldValues[changes.LayerGuid] = 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))
+                if (LastChanges[layerChange.LayerGuid].PixelChanges.ChangedPixels.ContainsKey(change.Key))
                 {
                     continue;
                 }
                 else
                 {
-                    LastChanges[layerChange.LayerIndex].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
+                    LastChanges[layerChange.LayerGuid].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))
+                if (LastOldValues[layerChange.LayerGuid].PixelChanges.ChangedPixels.ContainsKey(change.Key))
                 {
                     continue;
                 }
                 else
                 {
-                    LastOldValues[layerChange.LayerIndex].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
+                    LastOldValues[layerChange.LayerGuid].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
                 }
             }
         }

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

@@ -1,4 +1,6 @@
-using System.Windows.Input;
+using System.Linq;
+using System.Windows.Input;
+using PixiEditor.Helpers.Extensions;
 
 namespace PixiEditor.Models.Controllers.Shortcuts
 {
@@ -12,12 +14,25 @@ namespace PixiEditor.Models.Controllers.Shortcuts
             CommandParameter = commandParameter;
         }
 
+        public Shortcut(Key shortcutKey, ICommand command, string description, object commandParameter = null, ModifierKeys modifier = ModifierKeys.None)
+            : this(shortcutKey, command, commandParameter, modifier)
+        {
+            Description = description;
+        }
+
         public Key ShortcutKey { get; set; }
 
         public ModifierKeys Modifier { get; set; }
 
+        /// <summary>
+        /// Gets all <see cref="ModifierKeys"/> as an array.
+        /// </summary>
+        public ModifierKeys[] Modifiers { get => Modifier.GetFlags().Except(new ModifierKeys[] { ModifierKeys.None }).ToArray(); }
+
         public ICommand Command { get; set; }
 
+        public string Description { get; set; }
+
         public object CommandParameter { get; set; }
 
         public void Execute()

+ 5 - 4
PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Windows.Input;
 
@@ -6,14 +7,14 @@ namespace PixiEditor.Models.Controllers.Shortcuts
 {
     public class ShortcutController
     {
-        public ShortcutController()
+        public ShortcutController(params ShortcutGroup[] shortcutGroups)
         {
-            Shortcuts = new List<Shortcut>();
+            ShortcutGroups = new ObservableCollection<ShortcutGroup>(shortcutGroups);
         }
 
         public static bool BlockShortcutExecution { get; set; }
 
-        public List<Shortcut> Shortcuts { get; set; }
+        public ObservableCollection<ShortcutGroup> ShortcutGroups { get; init; }
 
         public Shortcut LastShortcut { get; private set; }
 
@@ -21,7 +22,7 @@ namespace PixiEditor.Models.Controllers.Shortcuts
         {
             if (!BlockShortcutExecution)
             {
-                Shortcut[] shortcuts = Shortcuts.FindAll(x => x.ShortcutKey == key).ToArray();
+                Shortcut[] shortcuts = ShortcutGroups.SelectMany(x => x.Shortcuts).ToList().FindAll(x => x.ShortcutKey == key).ToArray();
                 if (shortcuts.Length < 1)
                 {
                     return;

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