Ver Fonte

Merge branch 'master' into development

Krzysztof Krysiński há 2 anos atrás
pai
commit
1ae9a5445e
71 ficheiros alterados com 1038 adições e 152 exclusões
  1. 2 0
      src/ChunkyImageLib/ChunkyImageLib.csproj
  2. 10 0
      src/ChunkyImageLib/Surface.cs
  3. 4 0
      src/ChunkyImageLibTest/ChunkyImageLibTest.csproj
  4. 2 0
      src/ChunkyImageLibVis/ChunkyImageLibVis.csproj
  5. 3 2
      src/PixiEditor.ChangeableDocument.Gen/PixiEditor.ChangeableDocument.Gen.csproj
  6. 3 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/ReferenceLayerTopMost_ChangeInfo.cs
  7. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyReferenceLayer.cs
  8. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/ReferenceLayer.cs
  9. 16 0
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  10. 69 0
      src/PixiEditor.ChangeableDocument/Changes/Drawing/SelectionToMask_Change.cs
  11. 29 0
      src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/ReferenceLayerTopMost_Change.cs
  12. 2 0
      src/PixiEditor.ChangeableDocument/PixiEditor.ChangeableDocument.csproj
  13. 2 0
      src/PixiEditor.DrawingApi.Core/PixiEditor.DrawingApi.Core.csproj
  14. 2 0
      src/PixiEditor.DrawingApi.Skia/PixiEditor.DrawingApi.Skia.csproj
  15. 1 1
      src/PixiEditor.MSIX/Package.appxmanifest
  16. 34 1
      src/PixiEditor.MSIX/PixiEditor.MSIX.wapproj
  17. 1 0
      src/PixiEditor.UpdateInstaller/PixiEditor.UpdateInstaller.csproj
  18. 1 0
      src/PixiEditor.UpdateModule/PixiEditor.UpdateModule.csproj
  19. 2 0
      src/PixiEditor.Zoombox/PixiEditor.Zoombox.csproj
  20. 92 5
      src/PixiEditor.sln
  21. 40 0
      src/PixiEditor/Helpers/Converters/BoolToValueConverter.cs
  22. 81 0
      src/PixiEditor/Helpers/DocumentViewModelBuilder.cs
  23. 10 2
      src/PixiEditor/Helpers/Extensions/PixiParserDocumentEx.cs
  24. 1 1
      src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs
  25. BIN
      src/PixiEditor/Images/Commands/PixiEditor/Selection/AddToMask.png
  26. BIN
      src/PixiEditor/Images/Commands/PixiEditor/Selection/IntersectSelectionMask.png
  27. BIN
      src/PixiEditor/Images/Commands/PixiEditor/Selection/NewToMask.png
  28. BIN
      src/PixiEditor/Images/Commands/PixiEditor/Selection/SubtractFromMask.png
  29. 0 0
      src/PixiEditor/Images/Crop.png
  30. BIN
      src/PixiEditor/Images/ReferenceLayerAbove.png
  31. BIN
      src/PixiEditor/Images/ReferenceLayerBelow.png
  32. 16 0
      src/PixiEditor/Models/Commands/Attributes/Commands/FilterAttribute.cs
  33. 49 1
      src/PixiEditor/Models/Commands/CommandController.cs
  34. 1 1
      src/PixiEditor/Models/Commands/Commands/BasicCommand.cs
  35. 1 1
      src/PixiEditor/Models/Commands/Commands/Command.cs
  36. 1 1
      src/PixiEditor/Models/Commands/Commands/ToolCommand.cs
  37. 5 3
      src/PixiEditor/Models/Commands/Search/ColorSearchResult.cs
  38. 19 4
      src/PixiEditor/Models/Commands/Search/FileSearchResult.cs
  39. 1 1
      src/PixiEditor/Models/Commands/Search/SearchResult.cs
  40. 9 0
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  41. 23 0
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  42. 3 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformReferenceLayerExecutor.cs
  43. 23 6
      src/PixiEditor/Models/IO/Importer.cs
  44. 36 5
      src/PixiEditor/PixiEditor.csproj
  45. 2 2
      src/PixiEditor/Properties/AssemblyInfo.cs
  46. 3 2
      src/PixiEditor/Properties/Settings.settings
  47. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentManagerViewModel.cs
  48. 40 2
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.Serialization.cs
  49. 13 1
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs
  50. 39 0
      src/PixiEditor/ViewModels/SubViewModels/Document/ReferenceLayerViewModel.cs
  51. 35 2
      src/PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs
  52. 45 1
      src/PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs
  53. 21 25
      src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  54. 15 0
      src/PixiEditor/ViewModels/SubViewModels/Main/SearchViewModel.cs
  55. 23 0
      src/PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs
  56. 14 3
      src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  57. 8 1
      src/PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/UpdateSettings.cs
  58. 1 1
      src/PixiEditor/Views/Dialogs/AboutPopup.xaml
  59. 7 3
      src/PixiEditor/Views/Dialogs/CommandDebugPopup.xaml.cs
  60. 4 1
      src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml
  61. 7 0
      src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs
  62. 9 3
      src/PixiEditor/Views/Dialogs/SettingsWindow.xaml
  63. 18 0
      src/PixiEditor/Views/MainWindow.xaml
  64. 1 1
      src/PixiEditor/Views/UserControls/CommandSearch/CommandSearchControl.xaml
  65. 14 0
      src/PixiEditor/Views/UserControls/CommandSearch/CommandSearchControl.xaml.cs
  66. 44 8
      src/PixiEditor/Views/UserControls/CommandSearch/CommandSearchControlHelper.cs
  67. 14 4
      src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml
  68. 1 1
      src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml.cs
  69. 57 52
      src/PixiEditor/Views/UserControls/Viewport.xaml
  70. 4 3
      src/PixiEditorGen/PixiEditorGen.csproj
  71. 2 0
      src/PixiEditorTests/PixiEditorTests.csproj

+ 2 - 0
src/ChunkyImageLib/ChunkyImageLib.csproj

@@ -6,6 +6,8 @@
     <Nullable>enable</Nullable>
     <WarningsAsErrors>Nullable</WarningsAsErrors>
     <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+    <Configurations>Debug;Release;Steam</Configurations>
+    <Platforms>AnyCPU;x64;x86</Platforms>
   </PropertyGroup>
 
   <ItemGroup>

+ 10 - 0
src/ChunkyImageLib/Surface.cs

@@ -63,6 +63,16 @@ public class Surface : IDisposable
         return surface;
     }
 
+    public static Surface Load(byte[] encoded)
+    {
+        using var image = Image.FromEncodedData(encoded);
+
+        var surface = new Surface(new VecI(image.Width, image.Height));
+        surface.DrawingSurface.Canvas.DrawImage(image, 0, 0);
+
+        return surface;
+    }
+
     public unsafe void DrawBytes(VecI size, byte[] bytes, ColorType colorType, AlphaType alphaType)
     {
         ImageInfo info = new ImageInfo(size.X, size.Y, colorType, alphaType);

+ 4 - 0
src/ChunkyImageLibTest/ChunkyImageLibTest.csproj

@@ -5,6 +5,10 @@
     <Nullable>enable</Nullable>
 
     <IsPackable>false</IsPackable>
+
+    <Configurations>Debug;Release;Steam</Configurations>
+
+    <Platforms>AnyCPU;x64;x86</Platforms>
   </PropertyGroup>
 
   <ItemGroup>

+ 2 - 0
src/ChunkyImageLibVis/ChunkyImageLibVis.csproj

@@ -5,6 +5,8 @@
     <TargetFramework>net6.0-windows</TargetFramework>
     <Nullable>enable</Nullable>
     <UseWPF>true</UseWPF>
+    <Configurations>Debug;Release;Steam</Configurations>
+    <Platforms>AnyCPU;x64;x86</Platforms>
   </PropertyGroup>
 
   <ItemGroup>

+ 3 - 2
src/PixiEditor.ChangeableDocument.Gen/PixiEditor.ChangeableDocument.Gen.csproj

@@ -6,13 +6,14 @@
     <Nullable>enable</Nullable>
     <ImplicitUsings>true</ImplicitUsings>
     <LangVersion>Latest</LangVersion>
+    <Configurations>Debug;Release;Steam</Configurations>
+    <Platforms>AnyCPU;x64;x86</Platforms>
   </PropertyGroup>
 
   <ItemGroup>
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
   </ItemGroup>
   <ItemGroup>
-    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true"
-        PackagePath="analyzers/dotnet/cs" Visible="false" />
+    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
   </ItemGroup>
 </Project>

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/ReferenceLayerTopMost_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+
+public record ReferenceLayerTopMost_ChangeInfo(bool IsTopMost) : IChangeInfo;

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyReferenceLayer.cs

@@ -8,4 +8,5 @@ public interface IReadOnlyReferenceLayer
     public VecI ImageSize { get; }
     public ShapeCorners Shape { get; }
     public bool IsVisible { get; }
+    public bool IsTopMost { get; }
 }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/ReferenceLayer.cs

@@ -10,6 +10,7 @@ public class ReferenceLayer : IReadOnlyReferenceLayer
     public VecI ImageSize { get; }
     public ShapeCorners Shape { get; set; }
     public bool IsVisible { get; set; } = true;
+    public bool IsTopMost { get; set; }
     
     public ReferenceLayer(ImmutableArray<byte> imagePbgra32Bytes, VecI imageSize, ShapeCorners shape)
     {

+ 16 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -189,6 +189,22 @@ public static class FloodFillHelper
         return pixelStates;
     }
 
+    public static Surface FillSelection(IReadOnlyDocument document, VectorPath selection)
+    {
+        Surface surface = new Surface(document.Size);
+
+        var inverse = new VectorPath();
+        inverse.AddRect(new RectI(new(0, 0), document.Size));
+
+        surface.DrawingSurface.Canvas.Clear(new Color(255, 255, 255, 255));
+        surface.DrawingSurface.Canvas.Flush();
+        surface.DrawingSurface.Canvas.ClipPath(inverse.Op(selection, VectorPathOp.Difference));
+        surface.DrawingSurface.Canvas.Clear(new Color(0, 0, 0, 0));
+        surface.DrawingSurface.Canvas.Flush();
+
+        return surface;
+    }
+
     /// <summary>
     /// Use skia to set all pixels in array that are inside selection to InSelection
     /// </summary>

+ 69 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/SelectionToMask_Change.cs

@@ -0,0 +1,69 @@
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
+using BlendMode = PixiEditor.DrawingApi.Core.Surface.BlendMode;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+
+internal class SelectionToMask_Change : Change
+{
+    private readonly SelectionMode mode;
+    private readonly Guid targetMember;
+    private Changeables.Selection? selection;
+    private CommittedChunkStorage? chunkStorage = null;
+    
+    [GenerateMakeChangeAction]
+    public SelectionToMask_Change(Guid targetMember, SelectionMode mode)
+    {
+        this.targetMember = targetMember;
+        this.mode = mode;
+    }
+    
+    public override bool InitializeAndValidate(Document target)
+    {
+        selection = target.Selection;
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        var image = DrawingChangeHelper.GetTargetImageOrThrow(target, targetMember, true);
+        
+        VectorPath? selection = target.Selection.SelectionPath.IsEmpty ? null : target.Selection.SelectionPath;
+        HashSet<Guid> membersToReference = new();
+        membersToReference.Add(targetMember);
+        
+        var blendMode = mode switch
+        {
+            SelectionMode.New => BlendMode.DstATop,
+            SelectionMode.Add => BlendMode.Plus,
+            SelectionMode.Subtract => BlendMode.DstOut,
+            SelectionMode.Intersect => BlendMode.SrcIn
+        };
+
+        image.SetBlendMode(blendMode);
+
+        var selectionImage = FloodFillHelper.FillSelection(target, selection!);
+        
+        selectionImage.SaveToDesktop();
+
+        image.EnqueueDrawImage(new VecI(0, 0), selectionImage);
+        
+        var affArea = image.FindAffectedArea();
+        chunkStorage = new CommittedChunkStorage(image, affArea.Chunks);
+        image.CommitChanges();
+
+        ignoreInUndo = false;
+        return DrawingChangeHelper.CreateAreaChangeInfo(targetMember, affArea, true);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var affArea = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(target, targetMember, true, ref chunkStorage);
+        return DrawingChangeHelper.CreateAreaChangeInfo(targetMember, affArea, true);
+    }
+}

+ 29 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/ReferenceLayerTopMost_Change.cs

@@ -0,0 +1,29 @@
+using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+
+namespace PixiEditor.ChangeableDocument.Changes.Root.ReferenceLayerChanges;
+
+internal class ReferenceLayerTopMost_Change : Change
+{
+    private bool isTopMost;
+
+    [GenerateMakeChangeAction]
+    public ReferenceLayerTopMost_Change(bool isTopMost)
+    {
+        this.isTopMost = isTopMost;
+    }
+
+    public override bool InitializeAndValidate(Document target) => true;
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        target.ReferenceLayer!.IsTopMost = isTopMost;
+        ignoreInUndo = false;
+        return new ReferenceLayerTopMost_ChangeInfo(isTopMost);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.ReferenceLayer!.IsTopMost = !isTopMost;
+        return new ReferenceLayerTopMost_ChangeInfo(!isTopMost);
+    }
+}

+ 2 - 0
src/PixiEditor.ChangeableDocument/PixiEditor.ChangeableDocument.csproj

@@ -6,6 +6,8 @@
     <Nullable>enable</Nullable>
     <WarningsAsErrors>Nullable</WarningsAsErrors>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Configurations>Debug;Release;Steam</Configurations>
+    <Platforms>AnyCPU;x64;x86</Platforms>
   </PropertyGroup>
 
   <ItemGroup>

+ 2 - 0
src/PixiEditor.DrawingApi.Core/PixiEditor.DrawingApi.Core.csproj

@@ -5,5 +5,7 @@
         <Nullable>enable</Nullable>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
         <LangVersion>10</LangVersion>
+        <Configurations>Debug;Release;Steam</Configurations>
+        <Platforms>AnyCPU;x64;x86</Platforms>
     </PropertyGroup>
 </Project>

+ 2 - 0
src/PixiEditor.DrawingApi.Skia/PixiEditor.DrawingApi.Skia.csproj

@@ -4,6 +4,8 @@
         <TargetFramework>netstandard2.1</TargetFramework>
         <Nullable>enable</Nullable>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+        <Configurations>Debug;Release;Steam</Configurations>
+        <Platforms>AnyCPU;x64;x86</Platforms>
     </PropertyGroup>
 
     <ItemGroup>

+ 1 - 1
src/PixiEditor.MSIX/Package.appxmanifest

@@ -9,7 +9,7 @@
   <Identity
     Name="56069PixiEditorOrganizati.PixiEditor"
     Publisher="CN=0AFA75AD-56A3-481D-B5E4-D3C6274DD38A"
-    Version="0.2.0.0" />
+    Version="1.0.0.0" />
 
   <Properties>
     <DisplayName>PixiEditor</DisplayName>

+ 34 - 1
src/PixiEditor.MSIX/PixiEditor.MSIX.wapproj

@@ -51,7 +51,7 @@
   <Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
   <PropertyGroup>
     <ProjectGuid>1f97f972-f9e8-4f35-a8b5-3f71408d2230</ProjectGuid>
-    <TargetPlatformVersion>10.0.22000.0</TargetPlatformVersion>
+    <TargetPlatformVersion>10.0.22621.0</TargetPlatformVersion>
     <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
     <DefaultLanguage>en-US</DefaultLanguage>
     <AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
@@ -59,6 +59,39 @@
     <PackageCertificateThumbprint>4C83B3D55F197ED681F813F8BEB48ACDED28FD6F</PackageCertificateThumbprint>
     <PackageCertificateKeyFile>PixiEditor.MSIX_TemporaryKey.pfx</PackageCertificateKeyFile>
   </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <OutputPath>bin\Debug\</OutputPath>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
+    <OutputPath>bin\x64\Debug\</OutputPath>
+    <PlatformTarget>x64</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <OutputPath>bin\x86\Debug\</OutputPath>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <OutputPath>bin\Release\</OutputPath>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
+    <OutputPath>bin\x64\Release\</OutputPath>
+    <PlatformTarget>x64</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <OutputPath>bin\x86\Release\</OutputPath>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Steam|x86' ">
+    <OutputPath>bin\x86\Steam\</OutputPath>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Steam|AnyCPU' ">
+    <OutputPath>bin\Steam\</OutputPath>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Steam|x64' ">
+    <OutputPath>bin\x64\Steam\</OutputPath>
+    <PlatformTarget>x64</PlatformTarget>
+  </PropertyGroup>
   <ItemGroup>
     <AppxManifest Include="Package.appxmanifest">
       <SubType>Designer</SubType>

+ 1 - 0
src/PixiEditor.UpdateInstaller/PixiEditor.UpdateInstaller.csproj

@@ -6,6 +6,7 @@
     <UseWPF>true</UseWPF>
     <ApplicationManifest>app.manifest</ApplicationManifest>
     <Platforms>AnyCPU;x64;x86</Platforms>
+    <Configurations>Debug;Release;Steam</Configurations>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 0
src/PixiEditor.UpdateModule/PixiEditor.UpdateModule.csproj

@@ -3,6 +3,7 @@
   <PropertyGroup>
     <TargetFramework>net7.0</TargetFramework>
     <Platforms>AnyCPU;x64;x86</Platforms>
+    <Configurations>Debug;Release;Steam</Configurations>
   </PropertyGroup>
 
 </Project>

+ 2 - 0
src/PixiEditor.Zoombox/PixiEditor.Zoombox.csproj

@@ -5,6 +5,8 @@
     <Nullable>enable</Nullable>
     <UseWPF>true</UseWPF>
     <WarningsAsErrors>Nullable</WarningsAsErrors>
+    <Configurations>Debug;Release;Steam</Configurations>
+    <Platforms>AnyCPU;x64;x86</Platforms>
   </PropertyGroup>
 
   <ItemGroup>

+ 92 - 5
src/PixiEditor.sln

@@ -32,11 +32,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.ChangeableDocume
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.Zoombox", "PixiEditor.Zoombox\PixiEditor.Zoombox.csproj", "{69DD5830-C682-49FB-B1A5-D2A506EEA06B}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.DrawingApi.Core", "PixiEditor.DrawingApi.Core\PixiEditor.DrawingApi.Core.csproj", "{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.DrawingApi.Core", "PixiEditor.DrawingApi.Core\PixiEditor.DrawingApi.Core.csproj", "{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.DrawingApi.Skia", "PixiEditor.DrawingApi.Skia\PixiEditor.DrawingApi.Skia.csproj", "{98040E8A-F08E-45F8-956F-6480C8272049}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.DrawingApi.Skia", "PixiEditor.DrawingApi.Skia\PixiEditor.DrawingApi.Skia.csproj", "{98040E8A-F08E-45F8-956F-6480C8272049}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditorGen", "PixiEditorGen\PixiEditorGen.csproj", "{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditorGen", "PixiEditorGen\PixiEditorGen.csproj", "{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -55,10 +55,13 @@ Global
 		Release|Any CPU = Release|Any CPU
 		Release|x64 = Release|x64
 		Release|x86 = Release|x86
+		Steam|Any CPU = Steam|Any CPU
+		Steam|x64 = Steam|x64
+		Steam|x86 = Steam|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|Any CPU.ActiveCfg = Debug|x86
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|Any CPU.Build.0 = Debug|x86
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x64.ActiveCfg = Debug|x64
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x64.Build.0 = Debug|x64
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x86.ActiveCfg = Debug|x86
@@ -87,6 +90,12 @@ Global
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|x64.Build.0 = Release|x64
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|x86.ActiveCfg = Release|x86
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Release|x86.Build.0 = Release|x86
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Steam|x64.ActiveCfg = Steam|x64
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Steam|x64.Build.0 = Steam|x64
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Steam|x86.ActiveCfg = Steam|x86
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Steam|x86.Build.0 = Steam|x86
 		{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|x64.ActiveCfg = Debug|x64
@@ -117,6 +126,12 @@ Global
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x64.Build.0 = Release|x64
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x86.ActiveCfg = Release|x86
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Release|x86.Build.0 = Release|x86
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Steam|x64.ActiveCfg = Steam|x64
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Steam|x64.Build.0 = Steam|x64
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Steam|x86.ActiveCfg = Steam|x86
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Steam|x86.Build.0 = Steam|x86
 		{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|x64.ActiveCfg = Debug|x64
@@ -147,6 +162,12 @@ Global
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x64.Build.0 = Release|x64
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x86.ActiveCfg = Release|x86
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Release|x86.Build.0 = Release|x86
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Steam|x64.ActiveCfg = Steam|x64
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Steam|x64.Build.0 = Steam|x64
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Steam|x86.ActiveCfg = Steam|x86
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Steam|x86.Build.0 = Steam|x86
 		{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|x64.ActiveCfg = Debug|x64
@@ -177,6 +198,12 @@ Global
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|x64.Build.0 = Release|x64
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|x86.ActiveCfg = Release|x86
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.Release|x86.Build.0 = Release|x86
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Steam|x64.ActiveCfg = Steam|x64
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Steam|x64.Build.0 = Steam|x64
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Steam|x86.ActiveCfg = Steam|x86
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Steam|x86.Build.0 = Steam|x86
 		{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
@@ -218,6 +245,12 @@ Global
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|Any CPU.Deploy.0 = Release|Any CPU
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|x64.ActiveCfg = Release|x64
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Release|x86.ActiveCfg = Release|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Steam|Any CPU.ActiveCfg = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Steam|Any CPU.Build.0 = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Steam|x64.ActiveCfg = Steam|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Steam|x64.Build.0 = Steam|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Steam|x86.ActiveCfg = Steam|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Steam|x86.Build.0 = Steam|x86
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -248,6 +281,12 @@ Global
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Release|x64.Build.0 = Release|Any CPU
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Release|x86.ActiveCfg = Release|Any CPU
 		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Release|x86.Build.0 = Release|Any CPU
+		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Steam|x64.ActiveCfg = Steam|x64
+		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Steam|x64.Build.0 = Steam|x64
+		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Steam|x86.ActiveCfg = Steam|x86
+		{6A9DA760-1E47-414C-B8E8-3B4927F18131}.Steam|x86.Build.0 = Steam|x86
 		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -278,6 +317,12 @@ Global
 		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Release|x64.Build.0 = Release|Any CPU
 		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Release|x86.ActiveCfg = Release|Any CPU
 		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Release|x86.Build.0 = Release|Any CPU
+		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Steam|x64.ActiveCfg = Steam|x64
+		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Steam|x64.Build.0 = Steam|x64
+		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Steam|x86.ActiveCfg = Steam|x86
+		{E31A8266-5BCA-4877-B9E5-9C5BB42829D6}.Steam|x86.Build.0 = Steam|x86
 		{510ED47C-2455-4DCE-A561-1074725E1236}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{510ED47C-2455-4DCE-A561-1074725E1236}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{510ED47C-2455-4DCE-A561-1074725E1236}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -308,6 +353,12 @@ Global
 		{510ED47C-2455-4DCE-A561-1074725E1236}.Release|x64.Build.0 = Release|Any CPU
 		{510ED47C-2455-4DCE-A561-1074725E1236}.Release|x86.ActiveCfg = Release|Any CPU
 		{510ED47C-2455-4DCE-A561-1074725E1236}.Release|x86.Build.0 = Release|Any CPU
+		{510ED47C-2455-4DCE-A561-1074725E1236}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{510ED47C-2455-4DCE-A561-1074725E1236}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{510ED47C-2455-4DCE-A561-1074725E1236}.Steam|x64.ActiveCfg = Steam|x64
+		{510ED47C-2455-4DCE-A561-1074725E1236}.Steam|x64.Build.0 = Steam|x64
+		{510ED47C-2455-4DCE-A561-1074725E1236}.Steam|x86.ActiveCfg = Steam|x86
+		{510ED47C-2455-4DCE-A561-1074725E1236}.Steam|x86.Build.0 = Steam|x86
 		{294FD171-9536-474C-A679-83F0266275FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{294FD171-9536-474C-A679-83F0266275FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{294FD171-9536-474C-A679-83F0266275FB}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -338,6 +389,12 @@ Global
 		{294FD171-9536-474C-A679-83F0266275FB}.Release|x64.Build.0 = Release|Any CPU
 		{294FD171-9536-474C-A679-83F0266275FB}.Release|x86.ActiveCfg = Release|Any CPU
 		{294FD171-9536-474C-A679-83F0266275FB}.Release|x86.Build.0 = Release|Any CPU
+		{294FD171-9536-474C-A679-83F0266275FB}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{294FD171-9536-474C-A679-83F0266275FB}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{294FD171-9536-474C-A679-83F0266275FB}.Steam|x64.ActiveCfg = Steam|x64
+		{294FD171-9536-474C-A679-83F0266275FB}.Steam|x64.Build.0 = Steam|x64
+		{294FD171-9536-474C-A679-83F0266275FB}.Steam|x86.ActiveCfg = Steam|x86
+		{294FD171-9536-474C-A679-83F0266275FB}.Steam|x86.Build.0 = Steam|x86
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -368,6 +425,12 @@ Global
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Release|x64.Build.0 = Release|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Release|x86.ActiveCfg = Release|Any CPU
 		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Release|x86.Build.0 = Release|Any CPU
+		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Steam|x64.ActiveCfg = Steam|x64
+		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Steam|x64.Build.0 = Steam|x64
+		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Steam|x86.ActiveCfg = Steam|x86
+		{758DF7DF-A8B1-4409-B79A-018E542B7251}.Steam|x86.Build.0 = Steam|x86
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -398,6 +461,12 @@ Global
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x64.Build.0 = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x86.ActiveCfg = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x86.Build.0 = Release|Any CPU
+		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Steam|x64.ActiveCfg = Steam|x64
+		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Steam|x64.Build.0 = Steam|x64
+		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Steam|x86.ActiveCfg = Steam|x86
+		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Steam|x86.Build.0 = Steam|x86
 		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -428,6 +497,12 @@ Global
 		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|x64.Build.0 = Release|Any CPU
 		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|x86.ActiveCfg = Release|Any CPU
 		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|x86.Build.0 = Release|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Steam|x64.ActiveCfg = Steam|x64
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Steam|x64.Build.0 = Steam|x64
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Steam|x86.ActiveCfg = Steam|x86
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Steam|x86.Build.0 = Steam|x86
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -458,6 +533,12 @@ Global
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x64.Build.0 = Release|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x86.ActiveCfg = Release|Any CPU
 		{98040E8A-F08E-45F8-956F-6480C8272049}.Release|x86.Build.0 = Release|Any CPU
+		{98040E8A-F08E-45F8-956F-6480C8272049}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{98040E8A-F08E-45F8-956F-6480C8272049}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{98040E8A-F08E-45F8-956F-6480C8272049}.Steam|x64.ActiveCfg = Steam|x64
+		{98040E8A-F08E-45F8-956F-6480C8272049}.Steam|x64.Build.0 = Steam|x64
+		{98040E8A-F08E-45F8-956F-6480C8272049}.Steam|x86.ActiveCfg = Steam|x86
+		{98040E8A-F08E-45F8-956F-6480C8272049}.Steam|x86.Build.0 = Steam|x86
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -488,6 +569,12 @@ Global
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|x64.Build.0 = Release|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|x86.ActiveCfg = Release|Any CPU
 		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Release|x86.Build.0 = Release|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Steam|Any CPU.ActiveCfg = Steam|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Steam|Any CPU.Build.0 = Steam|Any CPU
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Steam|x64.ActiveCfg = Steam|x64
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Steam|x64.Build.0 = Steam|x64
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Steam|x86.ActiveCfg = Steam|x86
+		{1DC5B4C4-6902-4659-AE7E-17FDA0403DEB}.Steam|x86.Build.0 = Steam|x86
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 40 - 0
src/PixiEditor/Helpers/Converters/BoolToValueConverter.cs

@@ -0,0 +1,40 @@
+using System.Globalization;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class BoolToValueConverter : MarkupConverter
+{
+    public object FalseValue { get; set; }
+    
+    public object TrueValue { get; set; }
+    
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is bool boolean && boolean)
+        {
+            return TrueValue;
+        }
+
+        return FalseValue;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value == FalseValue)
+        {
+            return false;
+        }
+
+        if (value == TrueValue)
+        {
+            return true;
+        }
+
+        if (targetType == typeof(bool?))
+        {
+            return null;
+        }
+
+        throw new ArgumentException("value was neither FalseValue nor TrueValue and targetType was not a nullable bool");
+    }
+}

+ 81 - 0
src/PixiEditor/Helpers/DocumentViewModelBuilder.cs

@@ -1,5 +1,7 @@
 using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
 using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
@@ -15,6 +17,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     public List<Color> Swatches { get; set; } = new List<Color>();
     public List<Color> Palette { get; set; } = new List<Color>();
     
+    public ReferenceLayerBuilder ReferenceLayer { get; set; }
 
     public DocumentViewModelBuilder WithSize(int width, int height)
     {
@@ -42,6 +45,27 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     public DocumentViewModelBuilder WithPalette<T>(IEnumerable<T> pallet, Func<T, Color> toColor) =>
         WithPalette(pallet.Select(toColor));
 
+    public DocumentViewModelBuilder WithReferenceLayer<T>(T reference, Action<T, ReferenceLayerBuilder> builder)
+    {
+        if (reference != null)
+        {
+            WithReferenceLayer(x => builder(reference, x));
+        }
+
+        return this;
+    }
+    
+    public DocumentViewModelBuilder WithReferenceLayer(Action<ReferenceLayerBuilder> builder)
+    {
+        var reference = new ReferenceLayerBuilder();
+
+        builder(reference);
+
+        ReferenceLayer = reference;
+        
+        return this;
+    }
+    
     public abstract class StructureMemberBuilder
     {
         private MaskBuilder maskBuilder;
@@ -148,6 +172,8 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         
         public int OffsetY { get; set; }
         
+        public bool LockAlpha { get; set; }
+
         public new LayerBuilder WithName(string name) => base.WithName(name) as LayerBuilder;
         
         public new LayerBuilder WithVisibility(bool visibility) => base.WithVisibility(visibility) as LayerBuilder;
@@ -157,6 +183,12 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         public new LayerBuilder WithBlendMode(BlendMode blendMode) => base.WithBlendMode(blendMode) as LayerBuilder;
         
         public new LayerBuilder WithClipToBelow(bool value) => base.WithClipToBelow(value) as LayerBuilder;
+
+        public LayerBuilder WithLockAlpha(bool layerLockAlpha)
+        {
+            LockAlpha = layerLockAlpha;
+            return this;
+        }
         
         public new LayerBuilder WithMask(Action<MaskBuilder> mask) => base.WithMask(mask) as LayerBuilder;
         
@@ -276,6 +308,55 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
             return this;
         }
     }
+
+    public class ReferenceLayerBuilder
+    {
+        public bool IsVisible { get; set; }
+        
+        public bool IsTopmost { get; set; }
+        
+        public VecI ImageSize { get; set; }
+        
+        public ShapeCorners Shape { get; set; }
+        
+        public byte[] ImagePbgra32Bytes { get; set; }
+
+        public ReferenceLayerBuilder WithIsVisible(bool isVisible)
+        {
+            IsVisible = isVisible;
+            return this;
+        }
+
+        public ReferenceLayerBuilder WithIsTopmost(bool isTopmost)
+        {
+            IsTopmost = isTopmost;
+            return this;
+        }
+
+        public ReferenceLayerBuilder WithSurface(Surface surface)
+        {
+            var writeableBitmap = surface.ToWriteableBitmap();
+            byte[] bytes = new byte[writeableBitmap.PixelHeight * writeableBitmap.BackBufferStride];
+            Marshal.Copy(surface.ToWriteableBitmap().BackBuffer, bytes, 0, bytes.Length);
+
+            WithImage(surface.Size, bytes);
+            
+            return this;
+        }
+
+        public ReferenceLayerBuilder WithImage(VecI size, byte[] pbgraData)
+        {
+            ImageSize = size;
+            ImagePbgra32Bytes = pbgraData;
+            return this;
+        }
+
+        public ReferenceLayerBuilder WithRect(VecD offset, VecD size)
+        {
+            Shape = new ShapeCorners(new RectD(offset, size));
+            return this;
+        }
+    }
     
 }
 

+ 10 - 2
src/PixiEditor/Helpers/Extensions/PixiParserDocumentEx.cs

@@ -1,4 +1,7 @@
-using PixiEditor.DrawingApi.Core.ColorsImpl;
+using ChunkyImageLib;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Models.IO;
 using PixiEditor.Parser;
 using PixiEditor.ViewModels.SubViewModels.Document;
 
@@ -12,7 +15,11 @@ internal static class PixiParserDocumentEx
         {
             b.WithSize(document.Width, document.Height)
                 .WithPalette(document.Palette, x => new Color(x.R, x.G, x.B, x.A))
-                .WithSwatches(document.Swatches, x => new(x.R, x.G, x.B, x.A));
+                .WithSwatches(document.Swatches, x => new(x.R, x.G, x.B, x.A))
+                .WithReferenceLayer(document.ReferenceLayer, (r, builder) => builder
+                    .WithIsVisible(r.Enabled)
+                    .WithRect(new VecD(r.OffsetX, r.OffsetY), new VecD(r.Width, r.Height))
+                    .WithSurface(Surface.Load(r.ImageBytes)));
 
             BuildChildren(b, document.RootFolder.Children);
         });
@@ -54,6 +61,7 @@ internal static class PixiParserDocumentEx
                 .WithBlendMode((PixiEditor.ChangeableDocument.Enums.BlendMode)(int)layer.BlendMode)
                 .WithRect(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
                 .WithClipToBelow(layer.ClipToMemberBelow)
+                .WithLockAlpha(layer.LockAlpha)
                 .WithMask(layer.Mask,
                     (x, m) => x.WithVisibility(m.Enabled).WithSurface(m.Width, m.Height,
                         x => x.WithImage(m.ImageBytes, m.OffsetX, m.OffsetY)));

+ 1 - 1
src/PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs

@@ -50,8 +50,8 @@ internal static class ServiceCollectionHelpers
         .AddSingleton<ToolViewModel, PenToolViewModel>()
         .AddSingleton<ToolViewModel, SelectToolViewModel>()
         .AddSingleton<ToolViewModel, MagicWandToolViewModel>()
-        .AddSingleton<ToolViewModel, FloodFillToolViewModel>()
         .AddSingleton<ToolViewModel, LassoToolViewModel>()
+        .AddSingleton<ToolViewModel, FloodFillToolViewModel>()
         .AddSingleton<ToolViewModel, LineToolViewModel>()
         .AddSingleton<ToolViewModel, EllipseToolViewModel>()
         .AddSingleton<ToolViewModel, RectangleToolViewModel>()

BIN
src/PixiEditor/Images/Commands/PixiEditor/Selection/AddToMask.png


BIN
src/PixiEditor/Images/Commands/PixiEditor/Selection/IntersectSelectionMask.png


BIN
src/PixiEditor/Images/Commands/PixiEditor/Selection/NewToMask.png


BIN
src/PixiEditor/Images/Commands/PixiEditor/Selection/SubtractFromMask.png


+ 0 - 0
src/PixiEditor/Images/Commands/PixiEditor/Document/ClipCanvas.png → src/PixiEditor/Images/Crop.png


BIN
src/PixiEditor/Images/ReferenceLayerAbove.png


BIN
src/PixiEditor/Images/ReferenceLayerBelow.png


+ 16 - 0
src/PixiEditor/Models/Commands/Attributes/Commands/FilterAttribute.cs

@@ -0,0 +1,16 @@
+namespace PixiEditor.Models.Commands.Attributes.Commands;
+
+internal partial class Command
+{
+    public class FilterAttribute : CommandAttribute
+    {
+        public string SearchTerm { get; }
+        
+        public FilterAttribute(string internalName, string displayName, string searchTerm) : base(internalName, displayName, string.Empty)
+        {
+            SearchTerm = searchTerm;
+        }
+        
+        public FilterAttribute(string internalName) : base(internalName, null, null) { }
+    }
+}

+ 49 - 1
src/PixiEditor/Models/Commands/CommandController.cs

@@ -24,6 +24,10 @@ internal class CommandController
 
     public List<CommandGroup> CommandGroups { get; }
 
+    public OneToManyDictionary<string, Command> FilterCommands { get; }
+    
+    public Dictionary<string, string> FilterSearchTerm { get; }
+
     public Dictionary<string, CanExecuteEvaluator> CanExecuteEvaluators { get; }
 
     public Dictionary<string, IconEvaluator> IconEvaluators { get; }
@@ -39,6 +43,8 @@ internal class CommandController
 
         shortcutFile = new(ShortcutsPath, this);
 
+        FilterCommands = new();
+        FilterSearchTerm = new();
         Commands = new();
         CommandGroups = new();
         CanExecuteEvaluators = new();
@@ -211,11 +217,51 @@ internal class CommandController
                                 Parameter = basic.Parameter,
                             });
                     }
+                    else if (attribute is CommandAttribute.FilterAttribute menu)
+                    {
+                        string searchTerm = menu.SearchTerm;
+                        
+                        if (searchTerm == null)
+                        {
+                            searchTerm = FilterSearchTerm[menu.InternalName];
+                        }
+                        else
+                        {
+                            FilterSearchTerm.Add(menu.InternalName, menu.SearchTerm);
+                        }
+
+                        bool hasFilter = FilterCommands.ContainsKey(searchTerm);
+                        
+                        foreach (var menuCommand in commandAttrs.Where(x => x is not CommandAttribute.FilterAttribute))
+                        {
+                            FilterCommands.Add(searchTerm, Commands[menuCommand.InternalName]);
+                        }
+
+                        if (hasFilter)
+                            continue;
+
+                        var command =
+                            new Command.BasicCommand(
+                                _ => ViewModelMain.Current.SearchSubViewModel.OpenSearchWindow($":{searchTerm}:"),
+                                CanExecuteEvaluator.AlwaysTrue)
+                            {
+                                InternalName = menu.InternalName,
+                                DisplayName = menu.DisplayName,
+                                Description = string.Empty,
+                                IconEvaluator = IconEvaluator.Default,
+                                DefaultShortcut = menu.GetShortcut(),
+                                Shortcut = GetShortcut(name, attribute.GetShortcut(), template)
+                            };
+                        
+                        Commands.Add(command);
+
+                        AddCommandToCommandsCollection(command, commandGroupsData, commands);
+                    }
                 }
             }
         }
         
-        void AddCommand<TAttr, TCommand>(MethodInfo method, object instance, TAttr attribute,
+        TCommand AddCommand<TAttr, TCommand>(MethodInfo method, object instance, TAttr attribute,
             Func<bool, string, Action<object>, CanExecuteEvaluator, IconEvaluator, TCommand> commandFactory)
             where TAttr : CommandAttribute.CommandAttribute
             where TCommand : Command
@@ -264,6 +310,8 @@ internal class CommandController
 
             Commands.Add(command);
             AddCommandToCommandsCollection(command, commandGroupsData, commands);
+
+            return command;
         }
     }
 

+ 1 - 1
src/PixiEditor/Models/Commands/Commands/BasicCommand.cs

@@ -8,7 +8,7 @@ internal partial class Command
     {
         public object Parameter { get; init; }
 
-        protected override object GetParameter() => Parameter;
+        public override object GetParameter() => Parameter;
 
         public BasicCommand(Action<object> onExecute, CanExecuteEvaluator canExecute) : base(onExecute, canExecute) { }
     }

+ 1 - 1
src/PixiEditor/Models/Commands/Commands/Command.cs

@@ -40,7 +40,7 @@ internal abstract partial class Command : NotifyableObject
 
     public event ShortcutChangedEventHandler ShortcutChanged;
 
-    protected abstract object GetParameter();
+    public abstract object GetParameter();
 
     protected Command(Action<object> onExecute, CanExecuteEvaluator canExecute) =>
         Methods = new(this, onExecute, canExecute);

+ 1 - 1
src/PixiEditor/Models/Commands/Commands/ToolCommand.cs

@@ -11,7 +11,7 @@ internal partial class Command
 
         public Key TransientKey { get; init; }
 
-        protected override object GetParameter() => ToolType;
+        public override object GetParameter() => ToolType;
 
         public ToolCommand() : base(ViewModelMain.Current.ToolsSubViewModel.SetTool, CommandController.Current.CanExecuteEvaluators["PixiEditor.HasDocument"]) { }
     }

+ 5 - 3
src/PixiEditor/Models/Commands/Search/ColorSearchResult.cs

@@ -8,6 +8,7 @@ internal class ColorSearchResult : SearchResult
     private readonly DrawingApi.Core.ColorsImpl.Color color;
     private string text;
     private bool requiresDocument;
+    private bool isPalettePaste;
     private readonly Action<DrawingApi.Core.ColorsImpl.Color> target;
 
     public override string Text => text;
@@ -15,7 +16,7 @@ internal class ColorSearchResult : SearchResult
     public override string Description => $"{color} rgba({color.R}, {color.G}, {color.B}, {color.A})";
 
     //public override bool CanExecute => !requiresDocument || (requiresDocument && ViewModelMain.Current.BitmapManager.ActiveDocument != null);
-    public override bool CanExecute => true;
+    public override bool CanExecute => !isPalettePaste || ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument != null;
 
     public override ImageSource Icon => icon;
 
@@ -36,9 +37,10 @@ internal class ColorSearchResult : SearchResult
     public static ColorSearchResult PastePalette(DrawingApi.Core.ColorsImpl.Color color, string searchTerm = null)
     {
         //var result = new ColorSearchResult(color, x => ViewModelMain.Current.BitmapManager.ActiveDocument.Palette.Add(x))
-        var result = new ColorSearchResult(color, x => { })
+        var result = new ColorSearchResult(color, x => ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument!.Palette.Add(x))
         {
-            SearchTerm = searchTerm
+            SearchTerm = searchTerm,
+            isPalettePaste = true
         };
         result.text = $"Add color {color} to palette";
         result.requiresDocument = true;

+ 19 - 4
src/PixiEditor/Models/Commands/Search/FileSearchResult.cs

@@ -7,28 +7,43 @@ namespace PixiEditor.Models.Commands.Search;
 internal class FileSearchResult : SearchResult
 {
     private readonly DrawingImage icon;
+    private readonly bool asReferenceLayer;
 
     public string FilePath { get; }
 
-    public override string Text => $"...\\{Path.GetFileName(FilePath)}";
+    public override string Text => asReferenceLayer ? $"As reference: ...\\{Path.GetFileName(FilePath)}" : $"...\\{Path.GetFileName(FilePath)}";
 
     public override string Description => FilePath;
 
-    public override bool CanExecute => true;
+    public override bool CanExecute => !asReferenceLayer ||
+                CommandController.Current.Commands["PixiEditor.Clipboard.PasteReferenceLayerFromPath"].Methods.CanExecute(FilePath);
 
     public override ImageSource Icon => icon;
 
-    public FileSearchResult(string path)
+    public FileSearchResult(string path, bool asReferenceLayer = false)
     {
         FilePath = path;
         var drawing = new GeometryDrawing() { Brush = FileExtensionToColorConverter.GetBrush(FilePath) };
         var geometry = new RectangleGeometry(new(0, 0, 10, 10), 3, 3) { };
         drawing.Geometry = geometry;
         icon = new DrawingImage(drawing);
+        this.asReferenceLayer = asReferenceLayer;
     }
 
     public override void Execute()
     {
-        CommandController.Current.Commands["PixiEditor.File.OpenRecent"].Methods.Execute(FilePath);
+        if (!asReferenceLayer)
+        {
+            CommandController.Current.Commands["PixiEditor.File.OpenRecent"].Methods.Execute(FilePath);
+        }
+        else
+        {
+            var command = CommandController.Current.Commands["PixiEditor.Clipboard.PasteReferenceLayerFromPath"];
+            if (command.Methods.CanExecute(FilePath))
+            {
+                CommandController.Current.Commands["PixiEditor.Clipboard.PasteReferenceLayerFromPath"].Methods
+                    .Execute(FilePath);
+            }
+        }
     }
 }

+ 1 - 1
src/PixiEditor/Models/Commands/Search/SearchResult.cs

@@ -51,7 +51,7 @@ internal abstract class SearchResult : NotifyableObject
 
     private IEnumerable<Inline> GetInlines()
     {
-        if (Match == null)
+        if (Match is not { Success: true })
         {
             yield return new Run(Text);
             yield break;

+ 9 - 0
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -110,6 +110,9 @@ internal class DocumentUpdater
             case ReferenceLayerIsVisible_ChangeInfo info:
                 ProcessReferenceLayerIsVisible(info);
                 break;
+            case ReferenceLayerTopMost_ChangeInfo info:
+                ProcessReferenceLayerTopMost(info);
+                break;
             case SetSelectedMember_PassthroughAction info:
                 ProcessSetSelectedMember(info);
                 break;
@@ -122,6 +125,7 @@ internal class DocumentUpdater
             case ClearSoftSelectedMembers_PassthroughAction info:
                 ProcessClearSoftSelectedMembers(info);
                 break;
+                
         }
     }
 
@@ -145,6 +149,11 @@ internal class DocumentUpdater
         doc.ReferenceLayerViewModel.InternalSetReferenceLayer(info.ImagePbgra32Bytes, info.ImageSize, info.Shape);
     }
     
+    private void ProcessReferenceLayerTopMost(ReferenceLayerTopMost_ChangeInfo info)
+    {
+        doc.ReferenceLayerViewModel.InternalSetReferenceLayerTopMost(info.IsTopMost);
+    }
+
     private void ProcessRemoveSoftSelectedMember(RemoveSoftSelectedMember_PassthroughAction info)
     {
         StructureMemberViewModel? member = doc.StructureHelper.Find(info.GuidValue);

+ 23 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -5,6 +5,7 @@ using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Enums;
@@ -536,4 +537,26 @@ internal class DocumentOperationsModule
             new EndTransformReferenceLayer_Action()
             );
     }
+
+    public void SelectionToMask(SelectionMode mode)
+    {
+        if (Document.SelectedStructureMember is not { } member || Document.SelectionPathBindable.IsEmpty)
+            return;
+
+        if (!Document.SelectedStructureMember.HasMaskBindable)
+        {
+            Internals.ActionAccumulator.AddActions(new CreateStructureMemberMask_Action(member.GuidValue));
+        }
+        
+        Internals.ActionAccumulator.AddFinishedActions(new SelectionToMask_Action(member.GuidValue, mode));
+    }
+
+    public void InvertSelection()
+    {
+        var selection = Document.SelectionPathBindable;
+        var inverse = new VectorPath();
+        inverse.AddRect(new RectI(new(0, 0), Document.SizeBindable));
+
+        Internals.ActionAccumulator.AddFinishedActions(new SetSelection_Action(inverse.Op(selection, VectorPathOp.Difference)));
+    }
 }

+ 3 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformReferenceLayerExecutor.cs

@@ -17,6 +17,7 @@ internal class TransformReferenceLayerExecutor : UpdateableChangeExecutor
 
         ShapeCorners corners = document.ReferenceLayerViewModel.ReferenceShapeBindable;
         document.TransformViewModel.ShowTransform(DocumentTransformMode.Scale_Rotate_Shear_NoPerspective, true, corners, true);
+        document.ReferenceLayerViewModel.IsTransforming = true;
         internals!.ActionAccumulator.AddActions(new TransformReferenceLayer_Action(corners));
         return ExecutionState.Success;
     }
@@ -36,6 +37,7 @@ internal class TransformReferenceLayerExecutor : UpdateableChangeExecutor
     {
         internals!.ActionAccumulator.AddFinishedActions(new EndTransformReferenceLayer_Action());
         document!.TransformViewModel.HideTransform();
+        document.ReferenceLayerViewModel.IsTransforming = false;
         onEnded!.Invoke(this);
     }
 
@@ -43,5 +45,6 @@ internal class TransformReferenceLayerExecutor : UpdateableChangeExecutor
     {
         internals!.ActionAccumulator.AddFinishedActions(new EndTransformReferenceLayer_Action());
         document!.TransformViewModel.HideTransform();
+        document.ReferenceLayerViewModel.IsTransforming = false;
     }
 }

+ 23 - 6
src/PixiEditor/Models/IO/Importer.cs

@@ -53,11 +53,11 @@ internal class Importer : NotifyableObject
         }
         catch (NotSupportedException)
         {
-            throw new CorruptedFileException();
+            throw new CorruptedFileException($"The file type '{Path.GetExtension(path)}' is not supported");
         }
         catch (FileFormatException)
         {
-            throw new CorruptedFileException();
+            throw new CorruptedFileException("The file appears to be corrupted");
         }
     }
 
@@ -77,9 +77,9 @@ internal class Importer : NotifyableObject
                 doc.FullFilePath = path;
                 return doc;
             }
-            catch (InvalidFileException)
+            catch (InvalidFileException e)
             {
-                throw new CorruptedFileException();
+                throw new CorruptedFileException("The given file seems to be corrupted or from a newer version of PixiEditor", e);
             }
         }
     }
@@ -100,13 +100,30 @@ internal class Importer : NotifyableObject
                 doc.FullFilePath = originalFilePath;
                 return doc;
             }
-            catch (InvalidFileException)
+            catch (InvalidFileException e)
             {
-                throw new CorruptedFileException();
+                throw new CorruptedFileException("The given file seems to be corrupted or from a newer version of PixiEditor", e);
             }
         }
     }
 
+    public static WriteableBitmap GetPreviewBitmap(string path)
+    {
+        if (!IsSupportedFile(path))
+        {
+            throw new ArgumentException($"The file type '{Path.GetExtension(path)}' is not supported");
+        }
+        
+        try
+        {
+            return Path.GetExtension(path) != ".pixi" ? ImportWriteableBitmap(path) : PixiParser.Deserialize(path).ToDocument().PreviewBitmap;
+        }
+        catch (InvalidFileException)
+        {
+            throw new CorruptedFileException();
+        }
+    }
+
     public static bool IsSupportedFile(string path)
     {
         return SupportedFilesHelper.IsSupportedFile(path);

+ 36 - 5
src/PixiEditor/PixiEditor.csproj

@@ -14,11 +14,13 @@
 		<PackageIcon>icon.ico</PackageIcon>
 		<ApplicationIcon>..\icon.ico</ApplicationIcon>
 		<Authors>Krzysztof Krysiński, Egor Mozgovoy, CPK</Authors>
-		<Configurations>Debug;Release;MSIX;MSIX Debug;Dev Release</Configurations>
+		<Configurations>Debug;Release;MSIX;MSIX Debug;Dev Release;Steam</Configurations>
 		<Platforms>AnyCPU;x64;x86</Platforms>
         <ImplicitUsings>true</ImplicitUsings>
         <AssemblyVersion></AssemblyVersion>
         <LangVersion>11</LangVersion>
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
 	</PropertyGroup>
 
 	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='MSIX|AnyCPU'">
@@ -51,7 +53,7 @@
 
 	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Dev Release|x86'">
 		<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-		<DefineConstants>TRACE;UPDATE</DefineConstants>
+		<DefineConstants>TRACE;UPDATE;RELEASE</DefineConstants>
 		<Optimize>True</Optimize>
 	</PropertyGroup>
 
@@ -62,7 +64,7 @@
 
 	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Dev Release|x64'">
 		<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-		<DefineConstants>TRACE;UPDATE</DefineConstants>
+		<DefineConstants>TRACE;UPDATE;RELEASE</DefineConstants>
 		<Optimize>True</Optimize>
 	</PropertyGroup>
 
@@ -127,6 +129,22 @@
 		<DefineConstants>DEBUG;TRACE</DefineConstants>
 	</PropertyGroup>
 
+	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Steam|x86'">
+	  <DefineConstants>TRACE;RELEASE;STEAM</DefineConstants>
+	  <Optimize>True</Optimize>
+	  <OutputPath>bin\x86\Steam\</OutputPath>
+	</PropertyGroup>
+
+	<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Steam|x64' ">
+	  <DefineConstants>TRACE;RELEASE;STEAM</DefineConstants>
+	  <Optimize>True</Optimize>
+	</PropertyGroup>
+
+	<PropertyGroup Condition=" '$(Configuration)' == 'Steam' ">
+	  <DefineConstants>TRACE;RELEASE;STEAM</DefineConstants>
+	  <Optimize>True</Optimize>
+	</PropertyGroup>
+
 	<ItemGroup>
 		<Compile Remove="Styles\AvalonDock\Images\**" />
 		<EmbeddedResource Remove="Styles\AvalonDock\Images\**" />
@@ -218,7 +236,7 @@
 		<PackageReference Include="Newtonsoft.Json" Version="13.0.2-beta2" />
 		<PackageReference Include="OneOf" Version="3.0.223" />
 		<PackageReference Include="PixiEditor.ColorPicker" Version="3.3.1" />
-		<PackageReference Include="PixiEditor.Parser" Version="3.1.0" />
+		<PackageReference Include="PixiEditor.Parser" Version="3.2.0" />
 		<PackageReference Include="PixiEditor.Parser.Skia" Version="3.0.0" />
 		<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
 		<PackageReference Include="WpfAnimatedGif" Version="2.0.2" />
@@ -316,7 +334,6 @@
 		<None Remove="Images\Commands\PixiEditor\View\ZoomOut.png" />
 		<Resource Include="Images\Commands\PixiEditor\View\ZoomOut.png" />
 		<None Remove="Images\Commands\PixiEditor\Document\ClipCanvas.png" />
-		<Resource Include="Images\Commands\PixiEditor\Document\ClipCanvas.png" />
 		<None Remove="Fonts\feather.ttf" />
 		<Resource Include="Fonts\feather.ttf" />
 		<None Remove="Images\Load.png" />
@@ -368,6 +385,20 @@
 		<Resource Include="Images\Commands\PixiEditor\Document\Rotate180DegLayers.png" />
 		<None Remove="Images\Commands\PixiEditor\Document\Rotate270DegLayers.png" />
 		<Resource Include="Images\Commands\PixiEditor\Document\Rotate270DegLayers.png" />
+		<None Remove="Images\Crop.png" />
+		<Resource Include="Images\Crop.png" />
+		<None Remove="Images\ReferenceLayerAbove.png" />
+		<Resource Include="Images\ReferenceLayerAbove.png" />
+		<None Remove="Images\ReferenceLayerBelow.png" />
+		<Resource Include="Images\ReferenceLayerBelow.png" />
+		<None Remove="Images\Commands\PixiEditor\Selection\SubtractFromMask.png" />
+		<Resource Include="Images\Commands\PixiEditor\Selection\SubtractFromMask.png" />
+		<None Remove="Images\Commands\PixiEditor\Selection\IntersectSelectionMask.png" />
+		<Resource Include="Images\Commands\PixiEditor\Selection\IntersectSelectionMask.png" />
+		<None Remove="Images\Commands\PixiEditor\Selection\AddToMask.png" />
+		<Resource Include="Images\Commands\PixiEditor\Selection\AddToMask.png" />
+		<None Remove="Images\Commands\PixiEditor\Selection\NewToMask.png" />
+		<Resource Include="Images\Commands\PixiEditor\Selection\NewToMask.png" />
 	</ItemGroup>
 	<ItemGroup>
 		<None Include="..\LICENSE">

+ 2 - 2
src/PixiEditor/Properties/AssemblyInfo.cs

@@ -50,5 +50,5 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.9.6")]
-[assembly: AssemblyFileVersion("0.1.9.6")]
+[assembly: AssemblyVersion("0.1.9.7")]
+[assembly: AssemblyFileVersion("0.1.9.7")]

+ 3 - 2
src/PixiEditor/Properties/Settings.settings

@@ -1,5 +1,6 @@
-<?xml version='1.0' encoding='utf-8'?>
+<?xml version="1.0" encoding="UTF-8"?>
 <SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
   <Profiles />
   <Settings />
-</SettingsFile>
+</SettingsFile>
+

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentManagerViewModel.cs

@@ -49,7 +49,7 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>
     [Evaluator.CanExecute("PixiEditor.HasDocument")]
     public bool DocumentNotNull() => ActiveDocument != null;
 
-    [Command.Basic("PixiEditor.Document.ClipCanvas", "Clip Canvas", "Clip Canvas", CanExecute = "PixiEditor.HasDocument")]
+    [Command.Basic("PixiEditor.Document.ClipCanvas", "Clip Canvas", "Clip Canvas", CanExecute = "PixiEditor.HasDocument", IconPath = "crop.png")]
     public void ClipCanvas() => ActiveDocument?.Operations.ClipCanvas();
 
     [Command.Basic("PixiEditor.Document.FlipImageHorizontal", FlipType.Horizontal, "Flip Image Horizontally", "Flip Image Horizontally", CanExecute = "PixiEditor.HasDocument")]

+ 40 - 2
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.Serialization.cs

@@ -8,7 +8,9 @@ using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.IO;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Collections;
 using BlendMode = PixiEditor.Parser.BlendMode;
@@ -31,12 +33,47 @@ internal partial class DocumentViewModel
         {
             Width = Width, Height = Height,
             Swatches = ToCollection(Swatches), Palette = ToCollection(Palette),
-            RootFolder = root, PreviewImage = (MaybeRenderWholeImage().Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray()
+            RootFolder = root, PreviewImage = (MaybeRenderWholeImage().Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
+            ReferenceLayer = GetReferenceLayer(doc)
         };
 
         return document;
     }
 
+    private static ReferenceLayer GetReferenceLayer(IReadOnlyDocument document)
+    {
+        if (document.ReferenceLayer == null)
+        {
+            return null;
+        }
+
+        var layer = document.ReferenceLayer!;
+
+        var surface = new Surface(new VecI(layer.ImageSize.X, layer.ImageSize.Y));
+        
+        surface.DrawBytes(surface.Size, layer.ImagePbgra32Bytes.ToArray(), ColorType.Bgra8888, AlphaType.Premul);
+
+        var encoder = new PngBitmapEncoder();
+
+        using var stream = new MemoryStream();
+        
+        encoder.Frames.Add(BitmapFrame.Create(surface.ToWriteableBitmap()));
+        encoder.Save(stream);
+
+        stream.Position = 0;
+
+        return new ReferenceLayer
+        {
+            Enabled = layer.IsVisible,
+            Width = (float)layer.Shape.RectSize.X,
+            Height = (float)layer.Shape.RectSize.Y,
+            OffsetX = (float)layer.Shape.TopLeft.X,
+            OffsetY = (float)layer.Shape.TopLeft.Y,
+            Opacity = 1,
+            ImageBytes = stream.ToArray()
+        };
+    }
+
     private static void AddMembers(IEnumerable<IReadOnlyStructureMember> members, IReadOnlyDocument document, Folder parent)
     {
         foreach (var member in members)
@@ -80,7 +117,8 @@ internal partial class DocumentViewModel
         {
             Width = result?.Size.X ?? 0, Height = result?.Size.Y ?? 0, OffsetX = tightBounds?.X ?? 0, OffsetY = tightBounds?.Y ?? 0,
             Enabled = layer.IsVisible, BlendMode = (BlendMode)(int)layer.BlendMode, ImageBytes = bytes,
-            ClipToMemberBelow = layer.ClipToMemberBelow, Name = layer.Name, 
+            ClipToMemberBelow = layer.ClipToMemberBelow, Name = layer.Name,
+            LockAlpha = layer.LockTransparency,
             Opacity = layer.Opacity, Mask = GetMask(layer.Mask, layer.MaskIsVisible)
         };
 

+ 13 - 1
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -1,4 +1,5 @@
-using System.IO;
+using System.Collections.Immutable;
+using System.IO;
 using System.Windows;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
@@ -201,6 +202,12 @@ internal partial class DocumentViewModel : NotifyableObject
             new SymmetryAxisPosition_Action(SymmetryAxisDirection.Vertical, builderInstance.Width / 2),
             new EndSymmetryAxisPosition_Action());
 
+        if (builderInstance.ReferenceLayer is { } refLayer)
+        {
+            acc
+                .AddActions(new SetReferenceLayer_Action(refLayer.Shape, refLayer.ImagePbgra32Bytes.ToImmutableArray(), refLayer.ImageSize));
+        }
+        
         viewModel.Swatches = new WpfObservableRangeCollection<Color>(builderInstance.Swatches);
         viewModel.Palette = new WpfObservableRangeCollection<Color>(builderInstance.Palette);
 
@@ -225,6 +232,11 @@ internal partial class DocumentViewModel : NotifyableObject
             
             acc.AddActions(new StructureMemberClipToMemberBelow_Action(member.ClipToMemberBelow, member.GuidValue));
 
+            if (member is DocumentViewModelBuilder.LayerBuilder layerBuilder)
+            {
+                acc.AddActions(new LayerLockTransparency_Action(layerBuilder.GuidValue, layerBuilder.LockAlpha));
+            }
+
             if (member is DocumentViewModelBuilder.LayerBuilder layer && layer.Surface is not null)
             {
                 PasteImage(member.GuidValue, layer.Surface, layer.Width, layer.Height, layer.OffsetX, layer.OffsetY, false);

+ 39 - 0
src/PixiEditor/ViewModels/SubViewModels/Document/ReferenceLayerViewModel.cs

@@ -53,6 +53,34 @@ internal class ReferenceLayerViewModel : INotifyPropertyChanged
         }
     }
 
+    private bool isTransforming;
+    public bool IsTransforming
+    {
+        get => isTransforming;
+        set
+        {
+            isTransforming = value;
+            RaisePropertyChanged(nameof(IsTransforming));
+            RaisePropertyChanged(nameof(ShowHighest));
+        }
+    }
+    
+    private bool isTopMost;
+    public bool IsTopMost
+    {
+        get => isTopMost;
+        set
+        {
+            if (!doc.UpdateableChangeActive)
+                internals.ActionAccumulator.AddFinishedActions(new ReferenceLayerTopMost_Action(value));
+        }
+    }
+    
+    public bool ShowHighest
+    {
+        get => IsTopMost || IsTransforming;
+    }
+
     public ReferenceLayerViewModel(DocumentViewModel doc, DocumentInternalParts internals)
     {
         this.doc = doc;
@@ -71,10 +99,14 @@ internal class ReferenceLayerViewModel : INotifyPropertyChanged
         ReferenceBitmap = WriteableBitmapHelpers.FromPbgra32Array(imagePbgra32Bytes.ToArray(), imageSize);
         referenceShape = shape;
         isVisible = true;
+        isTransforming = false;
+        isTopMost = false;
         RaisePropertyChanged(nameof(ReferenceBitmap));
         RaisePropertyChanged(nameof(ReferenceShapeBindable));
         RaisePropertyChanged(nameof(ReferenceTransformMatrix));
         RaisePropertyChanged(nameof(IsVisibleBindable));
+        RaisePropertyChanged(nameof(IsTransforming));
+        RaisePropertyChanged(nameof(ShowHighest));
     }
 
     public void InternalDeleteReferenceLayer()
@@ -99,5 +131,12 @@ internal class ReferenceLayerViewModel : INotifyPropertyChanged
         RaisePropertyChanged(nameof(IsVisibleBindable));
     }
 
+    public void InternalSetReferenceLayerTopMost(bool isTopMost)
+    {
+        this.isTopMost = isTopMost;
+        RaisePropertyChanged(nameof(IsTopMost));
+        RaisePropertyChanged(nameof(ShowHighest));
+    }
+
     #endregion
 }

+ 35 - 2
src/PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs

@@ -1,11 +1,12 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Text.RegularExpressions;
+using System.Collections.Immutable;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Media;
+using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Controllers;
+using PixiEditor.Models.IO;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main;
 #nullable enable
@@ -36,6 +37,38 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
         ClipboardController.TryPasteFromClipboard(Owner.DocumentManagerSubViewModel.ActiveDocument, pasteAsNewLayer);
     }
     
+    [Command.Basic("PixiEditor.Clipboard.PasteReferenceLayer", "Paste reference layer", "Paste reference layer from clipboard", CanExecute = "PixiEditor.Clipboard.CanPaste", IconPath = "Commands/PixiEditor/Clipboard/Paste.png")]
+    public void PasteReferenceLayer(DataObject data)
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+
+        var surface = (data == null ? ClipboardController.GetImagesFromClipboard() : ClipboardController.GetImage(data)).First();
+        using var image = surface.image;
+        
+        var bitmap = surface.image.ToWriteableBitmap();
+
+        byte[] pixels = new byte[bitmap.PixelWidth * bitmap.PixelHeight * 4];
+        bitmap.CopyPixels(pixels, bitmap.PixelWidth * 4, 0);
+
+        doc.Operations.ImportReferenceLayer(
+            pixels.ToImmutableArray(),
+            surface.image.Size);
+    }
+    
+    [Command.Internal("PixiEditor.Clipboard.PasteReferenceLayerFromPath")]
+    public void PasteReferenceLayer(string path)
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+
+        var bitmap = Importer.GetPreviewBitmap(path);
+        byte[] pixels = new byte[bitmap.PixelWidth * bitmap.PixelHeight * 4];
+        bitmap.CopyPixels(pixels, bitmap.PixelWidth * 4, 0);
+
+        doc.Operations.ImportReferenceLayer(
+            pixels.ToImmutableArray(),
+            new VecI(bitmap.PixelWidth, bitmap.PixelHeight));
+    }
+
     [Command.Basic("PixiEditor.Clipboard.PasteColor", false, "Paste color", "Paste color from clipboard", CanExecute = "PixiEditor.Clipboard.CanPasteColor", IconEvaluator = "PixiEditor.Clipboard.PasteColorIcon")]
     [Command.Basic("PixiEditor.Clipboard.PasteColorAsSecondary", true, "Paste color as secondary", "Paste color as secondary from clipboard", CanExecute = "PixiEditor.Clipboard.CanPasteColor", IconEvaluator = "PixiEditor.Clipboard.PasteColorIcon")]
     public void PasteColor(bool secondary)

+ 45 - 1
src/PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs

@@ -1,4 +1,5 @@
-using System.Windows.Input;
+using System.Windows;
+using System.Windows.Input;
 using System.Windows.Media;
 using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.Helpers;
@@ -81,6 +82,49 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>
         doc.Operations.ReplaceColor(colors.oldColor, colors.newColor);
     }
 
+    [Command.Basic("PixiEditor.Colors.ReplaceSecondaryByPrimaryColor", false, "Replace secondary color by primary", "Replace the secondary color by the primary color", IconEvaluator = "PixiEditor.Colors.ReplaceColorIcon")]
+    [Command.Basic("PixiEditor.Colors.ReplacePrimaryBySecondaryColor", true, "Replace primary color by secondary", "Replace the primary color by the secondary color", IconEvaluator = "PixiEditor.Colors.ReplaceColorIcon")]
+    public void ReplaceColors(bool replacePrimary)
+    {
+        var oldColor = replacePrimary ? PrimaryColor : SecondaryColor;
+        var newColor = replacePrimary ? SecondaryColor : PrimaryColor;
+        
+        ReplaceColors((oldColor, newColor));
+    }
+
+    [Evaluator.Icon("PixiEditor.Colors.ReplaceColorIcon")]
+    public ImageSource ReplaceColorsIcon(object command)
+    {
+        bool replacePrimary = command switch
+        {
+            CommandSearchResult result => (bool)result.Command.GetParameter(),
+            Models.Commands.Commands.Command cmd => (bool)cmd.GetParameter(),
+            _ => false
+        };
+        
+        var oldColor = replacePrimary ? PrimaryColor : SecondaryColor;
+        var newColor = replacePrimary ? SecondaryColor : PrimaryColor;
+        
+        var oldDrawing = new GeometryDrawing { Brush = new SolidColorBrush(oldColor.ToOpaqueMediaColor()), Pen = new(Brushes.Gray, .5) };
+        var oldGeometry = new EllipseGeometry(new Point(5, 5), 5, 5);
+        
+        oldDrawing.Geometry = oldGeometry;
+        
+        var newDrawing = new GeometryDrawing { Brush = new SolidColorBrush(newColor.ToOpaqueMediaColor()), Pen = new(Brushes.White, 1) };
+        var newGeometry = new EllipseGeometry(new Point(10, 10), 6, 6);
+
+        newDrawing.Geometry = newGeometry;
+        
+        return new DrawingImage(new DrawingGroup
+        {
+            Children = new DrawingCollection
+            {
+                oldDrawing,
+                newDrawing
+            }
+        });
+    }
+
     private async void OwnerOnStartupEvent(object sender, EventArgs e)
     {
         await ImportLospecPalette();

+ 21 - 25
src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -1,22 +1,17 @@
 using System.Collections.Immutable;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows.Input;
+using System.Windows.Media;
 using System.Windows.Media.Imaging;
-using ChunkyImageLib;
-using ChunkyImageLib.DataHolders;
 using Microsoft.Win32;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
-using PixiEditor.Helpers;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.IO;
 using PixiEditor.ViewModels.SubViewModels.Document;
-using PixiEditor.Views.Dialogs;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main;
 #nullable enable
@@ -378,24 +373,6 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
             size);
     }
 
-    [Command.Basic("PixiEditor.Layer.PasteReferenceLayer", "Paste reference layer", "Paste reference layer from clipboard", IconPath = "Commands/PixiEditor/Clipboard/Paste.png", CanExecute = "PixiEditor.Layer.ReferenceLayerDoesntExistAndHasClipboardContent")]
-    public void PasteReferenceLayer(DataObject data)
-    {
-        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
-
-        var surface = (data == null ? ClipboardController.GetImagesFromClipboard() : ClipboardController.GetImage(data)).First();
-        using var image = surface.image;
-        
-        var bitmap = surface.image.ToWriteableBitmap();
-
-        byte[] pixels = new byte[bitmap.PixelWidth * bitmap.PixelHeight * 4];
-        bitmap.CopyPixels(pixels, bitmap.PixelWidth * 4, 0);
-
-        doc.Operations.ImportReferenceLayer(
-            pixels.ToImmutableArray(),
-            surface.image.Size);
-    }
-    
     private string OpenReferenceLayerFilePicker()
     {
         var imagesFilter = new FileTypeDialogDataSet(FileTypeDialogDataSet.SetKind.Images).GetFormattedTypes();
@@ -419,7 +396,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         doc.Operations.DeleteReferenceLayer();
     }
 
-    [Command.Basic("PixiEditor.Layer.TransformReferenceLayer", "Transform reference layer", "Transform reference layer", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", IconPath = "Tools/MoveImage.png")]
+    [Command.Basic("PixiEditor.Layer.TransformReferenceLayer", "Transform reference layer", "Transform reference layer", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", IconPath = "crop.png")]
     public void TransformReferenceLayer()
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -429,6 +406,16 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         doc.Operations.TransformReferenceLayer();
     }
 
+    [Command.Basic("PixiEditor.Layer.ToggleReferenceLayerTopMost", "Toggle reference layer position", "Toggle reference layer between highest/lowest", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", IconEvaluator = "PixiEditor.Layer.ToggleReferenceLayerTopMostIcon")]
+    public void ToggleReferenceLayerTopMost()
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (doc is null)
+            return;
+
+        doc.ReferenceLayerViewModel.IsTopMost = !doc.ReferenceLayerViewModel.IsTopMost;
+    }
+
     [Command.Basic("PixiEditor.Layer.ResetReferenceLayerPosition", "Reset reference layer position", "Reset reference layer position", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", IconPath = "Layout.png")]
     public void ResetReferenceLayerPosition()
     {
@@ -439,4 +426,13 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         doc.Operations.ResetReferenceLayerPosition();
     }
 
+    [Evaluator.Icon("PixiEditor.Layer.ToggleReferenceLayerTopMostIcon")]
+    public ImageSource GetAboveEverythingReferenceLayerIcon()
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (doc is null || doc.ReferenceLayerViewModel.IsTopMost)
+            return new BitmapImage(new Uri("pack://application:,,,/Images/ReferenceLayerBelow.png"));
+
+        return new BitmapImage(new Uri("pack://application:,,,/Images/ReferenceLayerAbove.png"));
+    }
 }

+ 15 - 0
src/PixiEditor/ViewModels/SubViewModels/Main/SearchViewModel.cs

@@ -7,6 +7,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main;
 internal class SearchViewModel : SubViewModel<ViewModelMain>
 {
     private bool searchWindowOpen;
+    private bool selectAll;
     private string searchTerm;
 
     public bool SearchWindowOpen
@@ -21,6 +22,12 @@ internal class SearchViewModel : SubViewModel<ViewModelMain>
         set => SetProperty(ref searchTerm, value);
     }
 
+    public bool SelectAll
+    {
+        get => selectAll;
+        set => SetProperty(ref selectAll, value);
+    }
+
     public SearchViewModel(ViewModelMain owner) : base(owner)
     { }
 
@@ -30,10 +37,18 @@ internal class SearchViewModel : SubViewModel<ViewModelMain>
     [Command.Basic("PixiEditor.Search.Toggle", "", "Command Search", "Open the command search window", Key = Key.K, Modifiers = ModifierKeys.Control, CanExecute = "PixiEditor.Search.CanOpenSearchWindow")]
     public void ToggleSearchWindow(string searchTerm)
     {
+        SelectAll = true;
         SearchWindowOpen = !SearchWindowOpen;
         if (SearchWindowOpen)
         {
             SearchTerm = searchTerm;
         }
     }
+
+    public void OpenSearchWindow(string searchTerm, bool selectAll = true)
+    {
+        SelectAll = selectAll;
+        SearchWindowOpen = true;
+        SearchTerm = searchTerm;
+    }
 }

+ 23 - 0
src/PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs

@@ -1,4 +1,5 @@
 using System.Windows.Input;
+using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Models.Commands.Attributes.Commands;
 
@@ -30,12 +31,24 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
         doc.Operations.ClearSelection();
     }
 
+    [Command.Basic("PixiEditor.Selection.InvertSelection", "Invert selection", "Invert the selected area", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.I, Modifiers = ModifierKeys.Control)]
+    public void InvertSelection()
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.InvertSelection();
+    }
+
     [Evaluator.CanExecute("PixiEditor.Selection.IsNotEmpty")]
     public bool SelectionIsNotEmpty()
     {
         return !Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectionPathBindable?.IsEmpty ?? false;
     }
 
+    [Evaluator.CanExecute("PixiEditor.Selection.IsNotEmptyAndHasMask")]
+    public bool SelectionIsNotEmptyAndHasMask()
+    {
+        return SelectionIsNotEmpty() && (Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember?.HasMaskBindable ?? false);
+    }
+
     [Command.Basic("PixiEditor.Selection.TransformArea", "Transform selected area", "Transform selected area", CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.T, Modifiers = ModifierKeys.Control)]
     public void TransformSelectedArea()
     {
@@ -52,6 +65,16 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.NudgeSelectedObject(distance);
     }
 
+    [Command.Basic("PixiEditor.Selection.NewToMask", SelectionMode.New, "New mask from selection", "Selection to new mask", CanExecute = "PixiEditor.Selection.IsNotEmpty")]
+    [Command.Basic("PixiEditor.Selection.AddToMask", SelectionMode.Add, "Add selection to mask", "Add selection to mask", CanExecute = "PixiEditor.Selection.IsNotEmpty")]
+    [Command.Basic("PixiEditor.Selection.SubtractFromMask", SelectionMode.Subtract, "Subtract selection from mask", "Subtract selection from mask", CanExecute = "PixiEditor.Selection.IsNotEmptyAndHasMask")]
+    [Command.Basic("PixiEditor.Selection.IntersectSelectionMask", SelectionMode.Intersect, "Intersect selection with mask", "Intersect selection with mask", CanExecute = "PixiEditor.Selection.IsNotEmptyAndHasMask")]
+    [Command.Filter("PixiEditor.Selection.ToMaskMenu", "Selection to mask", "Selection to mask", Key = Key.M, Modifiers = ModifierKeys.Control)]
+    public void SelectionToMask(SelectionMode mode)
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.SelectionToMask(mode);
+    }
+
     [Evaluator.CanExecute("PixiEditor.Selection.CanNudgeSelectedObject")]
     public bool CanNudgeSelectedObject(int[] dist) => Owner.DocumentManagerSubViewModel.ActiveDocument?.UpdateableChangeActive == true;
 }

+ 14 - 3
src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs

@@ -20,7 +20,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
     public UpdateChecker UpdateChecker { get; set; }
 
-    public UpdateChannel[] UpdateChannels { get; } = new UpdateChannel[2];
+    public List<UpdateChannel> UpdateChannels { get; } = new List<UpdateChannel>();
 
     private string versionText;
 
@@ -211,8 +211,19 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
     private void InitUpdateChecker()
     {
-        UpdateChannels[0] = new UpdateChannel("Release", "PixiEditor", "PixiEditor");
-        UpdateChannels[1] = new UpdateChannel("Development", "PixiEditor", "PixiEditor-development-channel");
+#if UPDATE
+        UpdateChannels.Add(new UpdateChannel("Release", "PixiEditor", "PixiEditor"));
+        UpdateChannels.Add(new UpdateChannel("Development", "PixiEditor", "PixiEditor-development-channel"));
+#else
+    #if STEAM
+        string platformName = "Steam";
+    #elif MSIX
+        string platformName = "Microsoft Store";
+    #else
+        string platformName = "Unknown";
+    #endif
+        UpdateChannels.Add(new UpdateChannel(platformName, "", ""));
+#endif
 
         string updateChannel = IPreferences.Current.GetPreference<string>("UpdateChannel");
 

+ 8 - 1
src/PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/UpdateSettings.cs

@@ -15,7 +15,12 @@ internal class UpdateSettings : SettingsGroup
         }
     }
 
-    private string updateChannelName = GetPreference("UpdateChannel", "Release");
+    private string updateChannelName =
+#if UPDATE
+        GetPreference("UpdateChannel", "Release");
+#else
+        ViewModelMain.Current?.UpdateSubViewModel?.UpdateChannels?.FirstOrDefault()?.Name ?? "Unknown";
+#endif
 
     public string UpdateChannelName
     {
@@ -23,7 +28,9 @@ internal class UpdateSettings : SettingsGroup
         set
         {
             updateChannelName = value;
+#if UPDATE
             RaiseAndUpdatePreference("UpdateChannel", value);
+#endif
         }
     }
 

+ 1 - 1
src/PixiEditor/Views/Dialogs/AboutPopup.xaml

@@ -70,7 +70,7 @@
                 </Ellipse>
                 <Label Style="{StaticResource SettingsText}" Margin="10 0 0 0" FontSize="14">
                     <Hyperlink Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://github.com/CPKreuz" Style="{StaticResource SettingsLink}">
-                        <Run Text="Phillip Kreuz (CPK)"/>
+                        <Run Text="Philip Kreuz (CPK)"/>
                         <Run Text="" FontFamily="{StaticResource Feather}"/>
                     </Hyperlink>
                 </Label>

+ 7 - 3
src/PixiEditor/Views/Dialogs/CommandDebugPopup.xaml.cs

@@ -41,7 +41,7 @@ public partial class CommandDebugPopup : Window
 
             try
             {
-                image = command.GetIcon();
+                image = command.IconEvaluator.CallEvaluate(command, null);
             }
             catch (Exception e)
             {
@@ -92,9 +92,13 @@ public partial class CommandDebugPopup : Window
             }
         }
 
-        if (command.IconEvaluator != IconEvaluator.Default)
+        if (command.IconEvaluator == null)
         {
-            Info($"Uses custom icon evaluator ({command.IconEvaluator.GetType().Name})\n");
+            Warning("Icon evaluator is null");
+        }
+        else if (command.IconEvaluator != IconEvaluator.Default)
+        {
+            Info($"Uses custom icon evaluator ({command.IconEvaluator.Name})\n");
         }
 
         if (!string.IsNullOrWhiteSpace(command.IconPath))

+ 4 - 1
src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml

@@ -236,7 +236,10 @@
                     <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://www.youtube.com/channel/UCT5XvyvX1q5PAIaXfWmpsMQ"
                             Style="{StaticResource SocialMediaButton}" Tag="#FF0000" ToolTip="YouTube"
                             Content="/Images/SocialMedia/YouTubeIcon.png"/>
-                    <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}" CommandParameter="https://opencollective.com/pixieditor"
+                    <Button Command="{cmds:Command PixiEditor.Links.OpenHyperlink, UseProvided=True}"
+                            Visibility="{Binding ShowDonateButton,
+                            Converter={BoolToVisibilityConverter}}"
+                            CommandParameter="https://opencollective.com/pixieditor"
                             Style="{StaticResource SocialMediaButton}" Tag="#d4af37" ToolTip="Donate"
                             Content="/Images/SocialMedia/DonateIcon.png"/>
                 </uc:AlignableWrapPanel>

+ 7 - 0
src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs

@@ -39,6 +39,13 @@ internal partial class HelloTherePopup : Window
 
     public bool IsClosing { get; private set; }
 
+    public bool ShowDonateButton => // Steam doesn't allow external donations :(
+#if STEAM
+        false;
+#else
+        true;
+#endif
+
     public HelloTherePopup(FileViewModel fileViewModel)
     {
         DataContext = this;

+ 9 - 3
src/PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -125,14 +125,20 @@
 
                 <Label Grid.Row="9" Grid.ColumnSpan="2" Style="{StaticResource SettingsHeader}">Automatic updates</Label>
 
-                <CheckBox Grid.Row="10" Grid.Column="1" VerticalAlignment="Center"
+                <CheckBox Grid.Row="10" Grid.Column="1" VerticalAlignment="Center" IsEnabled="{Binding Path=ShowUpdateTab}"
                     IsChecked="{Binding SettingsSubViewModel.Update.CheckUpdatesOnStartup}">Check updates on startup</CheckBox>
 
                 <Label Grid.Row="11" Grid.Column="1" Style="{StaticResource SettingsText}">Update stream</Label>
-                <ComboBox Grid.Row="11" Grid.Column="2" VerticalAlignment="Center"
-                    Width="110" Height="21.96" HorizontalAlignment="Left"
+                <StackPanel Orientation="Horizontal" Grid.Row="11" Grid.Column="2" VerticalAlignment="Center"
+                            Height="21.96" HorizontalAlignment="Left">
+                <ComboBox Width="110" IsEnabled="{Binding Path=ShowUpdateTab}"
                     ItemsSource="{Binding SettingsSubViewModel.Update.UpdateChannels}"
                     SelectedValue="{Binding SettingsSubViewModel.Update.UpdateChannelName}"/>
+                <Image Cursor="Help" Margin="10 0 0 0" Source="/Images/Commands/PixiEditor/Links/OpenDocumentation.png"
+                       ToolTipService.InitialShowDelay="0"
+                       Visibility="{Binding Path=ShowUpdateTab, Converter={converters:InverseBoolToVisibilityConverter}}"
+                       ToolTip="Update channels can only be changed in standalone version (downloaded from https://pixieditor.net).&#x0a;Steam and Microsoft Store versions handle updates separately."/>
+                </StackPanel>
 
                 <Label Grid.Row="12" Grid.ColumnSpan="2" Style="{StaticResource SettingsHeader}">Debug</Label>
                 <CheckBox Grid.Row="13" Grid.Column="1" VerticalAlignment="Center"

+ 18 - 0
src/PixiEditor/Views/MainWindow.xaml

@@ -231,6 +231,24 @@
                         <MenuItem
                             Header="_Deselect"
                             cmds:Menu.Command="PixiEditor.Selection.Clear" />
+                        <MenuItem
+                            Header="_Invert"
+                            cmds:Menu.Command="PixiEditor.Selection.InvertSelection" />
+                        <Separator/>
+                        <MenuItem Header="Selection _to Mask">
+                            <MenuItem
+                                Header="to _new mask"
+                                cmds:Menu.Command="PixiEditor.Selection.NewToMask" />
+                            <MenuItem
+                                Header="_add to mask"
+                                cmds:Menu.Command="PixiEditor.Selection.AddToMask" />
+                            <MenuItem
+                                Header="_subtract from mask"
+                                cmds:Menu.Command="PixiEditor.Selection.SubtractFromMask" />
+                            <MenuItem
+                                Header="_intersect with mask"
+                                cmds:Menu.Command="PixiEditor.Selection.IntersectSelectionMask" />
+                        </MenuItem>
                     </MenuItem>
                     <MenuItem
                         Header="_Image">

+ 1 - 1
src/PixiEditor/Views/UserControls/CommandSearch/CommandSearchControl.xaml

@@ -24,7 +24,7 @@
                  Padding="5"
                  x:Name="textBox">
             <i:Interaction.Behaviors>
-                <behaves:TextBoxFocusBehavior SelectOnMouseClick="True" />
+                <behaves:TextBoxFocusBehavior SelectOnMouseClick="{Binding SelectAll, ElementName=uc, Mode=OneWay}" />
                 <behaves:GlobalShortcutFocusBehavior />
             </i:Interaction.Behaviors>
             <TextBox.Style>

+ 14 - 0
src/PixiEditor/Views/UserControls/CommandSearch/CommandSearchControl.xaml.cs

@@ -22,6 +22,15 @@ internal partial class CommandSearchControl : UserControl, INotifyPropertyChange
         get => (string)GetValue(SearchTermProperty);
         set => SetValue(SearchTermProperty, value);
     }
+    
+    public static readonly DependencyProperty SelectAllProperty =
+        DependencyProperty.Register(nameof(SelectAll), typeof(bool), typeof(CommandSearchControl));
+
+    public bool SelectAll
+    {
+        get => (bool)GetValue(SelectAllProperty);
+        set => SetValue(SelectAllProperty, value);
+    }
 
     private string warnings = "";
     public string Warnings
@@ -89,6 +98,11 @@ internal partial class CommandSearchControl : UserControl, INotifyPropertyChange
                     textBox.Focus();
                     UpdateSearchResults();
                     Mouse.Capture(this, CaptureMode.SubTree);
+
+                    if (!SelectAll)
+                    {
+                        textBox.CaretIndex = SearchTerm?.Length ?? 0;
+                    }
                 });
             }
         };

+ 44 - 8
src/PixiEditor/Views/UserControls/CommandSearch/CommandSearchControlHelper.cs

@@ -31,6 +31,36 @@ internal static class CommandSearchControlHelper
 
         var controller = CommandController.Current;
 
+        if (query.StartsWith(':') && query.Length > 1)
+        {
+            string searchTerm = query[1..].Replace(" ", "");
+            int index = searchTerm.IndexOf(':');
+
+            string menu;
+            string additional;
+            
+            if (index > 0)
+            {
+                menu = searchTerm[..index];
+                additional = searchTerm[(index + 1)..];
+            }
+            else
+            {
+                menu = searchTerm;
+                additional = string.Empty;
+            }
+
+            var menuCommands = controller.FilterCommands
+                .Where(x => x.Key.Replace(" ", "").Contains(menu, StringComparison.OrdinalIgnoreCase))
+                .SelectMany(x => x.Value);
+
+            newResults.AddRange(menuCommands
+                .Where(x => index == -1 || x.DisplayName.Replace(" ", "").Contains(additional, StringComparison.OrdinalIgnoreCase))
+                .Select(command => new CommandSearchResult(command) { SearchTerm = searchTerm }));
+
+            return (newResults, warnings);
+        }
+        
         // add matching colors
         MaybeParseColor(query).Switch(
             color =>
@@ -48,7 +78,7 @@ internal static class CommandSearchControlHelper
         // add matching commands
         newResults.AddRange(
             controller.Commands
-                .Where(x => x.Description.Contains(query, StringComparison.OrdinalIgnoreCase))
+                .Where(x => x.Description.Replace(" ", "").Contains(query.Replace(" ", ""), StringComparison.OrdinalIgnoreCase))
                 .Where(static x => ViewModelMain.Current.DebugSubViewModel.UseDebug ? true : !x.IsDebug)
                 .OrderByDescending(x => x.Description.Contains($" {query} ", StringComparison.OrdinalIgnoreCase))
                 .Take(18)
@@ -103,13 +133,19 @@ internal static class CommandSearchControlHelper
             files = files.Where(x => x.Contains(name, StringComparison.OrdinalIgnoreCase));
         }
 
-        return files
-            .Select(static file => Path.GetFullPath(file))
-            .Select(path => new FileSearchResult(path)
-            {
-                SearchTerm = name,
-                Match = Match($".../{Path.GetFileName(path)}", name ?? "")
-            });
+        string[] array = files as string[] ?? files.ToArray();
+        
+        if (array.Length != 1)
+        {
+            return array
+                .Select(static file => Path.GetFullPath(file))
+                .Select(path => new FileSearchResult(path)
+                {
+                    SearchTerm = name, Match = Match($".../{Path.GetFileName(path)}", name ?? "")
+                });
+        }
+
+        return array.Length >= 1 ? new[] { new FileSearchResult(array[0]), new FileSearchResult(array[0], true) } : ArraySegment<SearchResult>.Empty;
     }
 
     private static bool GetDirectory(string path, out string directory, out string file)

+ 14 - 4
src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml

@@ -89,6 +89,16 @@
                             IsThreeState="False" HorizontalAlignment="Center" 
                             IsChecked="{Binding Path=Document.ReferenceLayerViewModel.IsVisibleBindable, Mode=TwoWay, ElementName=uc}"/>
                     </Grid>
+                    <Button Cursor="Hand" DockPanel.Dock="Left"
+                            Command="{cmds:Command PixiEditor.Layer.ToggleReferenceLayerTopMost}"
+                            Style="{StaticResource ImageButtonStyle}" 
+                            ToolTip="{Binding Document.ReferenceLayerViewModel.IsTopMost, ElementName=uc, Converter={converters:BoolToValueConverter FalseValue='Put reference layer above', TrueValue='Put reference layer below'}}"
+                            RenderOptions.BitmapScalingMode="HighQuality"
+                            Width="20" Height="20" HorizontalAlignment="Right">
+                        <Button.Background>
+                            <ImageBrush ImageSource="{Binding Document.ReferenceLayerViewModel.IsTopMost, ElementName=uc, Converter={converters:BoolToValueConverter FalseValue='Images/ReferenceLayerBelow.png', TrueValue='Images/ReferenceLayerAbove.png'}}"/>
+                        </Button.Background>
+                    </Button>
                     <Border 
                         HorizontalAlignment="Left" DockPanel.Dock="Left"
                         Width="30" Height="30"
@@ -124,14 +134,14 @@
                             Style="{StaticResource ImageButtonStyle}" 
                             ToolTip="Transform reference layer"
                             RenderOptions.BitmapScalingMode="HighQuality"
-                            Width="25" Height="25" HorizontalAlignment="Right">
+                            Width="20" Height="20" HorizontalAlignment="Right">
                         <Button.Background>
-                            <ImageBrush ImageSource="/Images/Tools/MoveImage.png"/>
+                            <ImageBrush ImageSource="/Images/Crop.png"/>
                         </Button.Background>
                     </Button>
                     <TextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" HorizontalAlignment="Center"
-                                Margin="0 0 5 0" Foreground="White" 
-                                FontSize="15" VerticalAlignment="Center">Reference Layer</TextBlock>
+                               Margin="0 0 5 0" Foreground="White" 
+                               FontSize="15" VerticalAlignment="Center">Reference</TextBlock>
                 </DockPanel>
             </Grid>
         </DockPanel>

+ 1 - 1
src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml.cs

@@ -26,7 +26,7 @@ internal partial class ReferenceLayer : UserControl
 
     public ReferenceLayer()
     {
-        command = CommandController.Current.Commands["PixiEditor.Layer.PasteReferenceLayer"];
+        command = CommandController.Current.Commands["PixiEditor.Clipboard.PasteReferenceLayer"];
         InitializeComponent();
     }
 

+ 57 - 52
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -148,7 +148,11 @@
                     </ImageBrush>
                 </Border.Background>
                 <Grid>
-                    <Canvas Visibility="{Binding Source={vm:ToolVM ColorPickerToolViewModel}, Path=PickFromReferenceLayer, Converter={converters:BoolToVisibilityConverter}}">
+                    <Canvas
+                        Visibility="{Binding Source={vm:ToolVM ColorPickerToolViewModel}, Path=PickFromReferenceLayer, Converter={converters:BoolToVisibilityConverter}}"
+                        ZIndex="{Binding Document.ReferenceLayerViewModel.ShowHighest, Converter={converters:BoolToIntConverter}}"
+                        IsHitTestVisible="{Binding Document.ReferenceLayerViewModel.IsTransforming}"
+                        Opacity="{Binding Document.ReferenceLayerViewModel.ShowHighest, Converter={converters:BoolToValueConverter FalseValue=1.0, TrueValue=0.6}}">
                         <Image
                             Focusable="False"
                             Width="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Width}"
@@ -172,57 +176,58 @@
                         Source="{Binding TargetBitmap}"
                         Visibility="{Binding Source={vm:ToolVM ColorPickerToolViewModel}, Path=PickFromCanvas, Converter={converters:BoolToHiddenVisibilityConverter}}"
                         RenderOptions.BitmapScalingMode="{Binding Zoombox.Scale, Converter={converters:ScaleToBitmapScalingModeConverter}}"/>
-                    <symOverlay:SymmetryOverlay
-                        Focusable="False"
-                        IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"
-                        ZoomboxScale="{Binding Zoombox.Scale}"
-                        HorizontalAxisVisible="{Binding Document.HorizontalSymmetryAxisEnabledBindable}"
-                        VerticalAxisVisible="{Binding Document.VerticalSymmetryAxisEnabledBindable}"
-                        HorizontalAxisY="{Binding Document.HorizontalSymmetryAxisYBindable, Mode=OneWay}"
-                        VerticalAxisX="{Binding Document.VerticalSymmetryAxisXBindable, Mode=OneWay}"
-                        DragCommand="{cmds:Command PixiEditor.Document.DragSymmetry, UseProvided=True}"
-                        DragEndCommand="{cmds:Command PixiEditor.Document.EndDragSymmetry, UseProvided=True}" 
-                        DragStartCommand="{cmds:Command PixiEditor.Document.StartDragSymmetry, UseProvided=True}" />
-                    <overlays:SelectionOverlay
-                        Focusable="False"
-                        ShowFill="{Binding ToolsSubViewModel.ActiveTool, Source={vm:MainVM}, Converter={converters:IsSelectionToolConverter}}"
-                        Path="{Binding Document.SelectionPathBindable}"
-                        ZoomboxScale="{Binding Zoombox.Scale}" />
-                    <brushOverlay:BrushShapeOverlay
-                        Focusable="False"
-                        IsHitTestVisible="False"
-                        Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={converters:InverseBoolToVisibilityConverter}}"
-                        ZoomboxScale="{Binding Zoombox.Scale}"
-                        MouseEventSource="{Binding Zoombox.Tag.BackgroundGrid, Mode=OneTime}"
-                        MouseReference="{Binding Zoombox.Tag.MainImage, Mode=OneTime}"
-                        BrushSize="{Binding ToolsSubViewModel.ActiveBasicToolbar.ToolSize, Source={vm:MainVM}}"
-                        BrushShape="{Binding ToolsSubViewModel.ActiveTool.BrushShape, Source={vm:MainVM}, FallbackValue={x:Static brushOverlay:BrushShape.Hidden}}"
-                        />
-                    <transformOverlay:TransformOverlay
-                        Focusable="False"
-                        Cursor="Arrow"
-                        IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"
-                        HorizontalAlignment="Stretch"
-                        VerticalAlignment="Stretch"
-                        Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={converters:BoolToVisibilityConverter}}"
-                        ActionCompleted="{Binding Document.TransformViewModel.ActionCompletedCommand}"
-                        Corners="{Binding Document.TransformViewModel.Corners, Mode=TwoWay}"
-                        RequestedCorners="{Binding Document.TransformViewModel.RequestedCorners, Mode=TwoWay}"
-                        CornerFreedom="{Binding Document.TransformViewModel.CornerFreedom}"
-                        SideFreedom="{Binding Document.TransformViewModel.SideFreedom}"
-                        LockRotation="{Binding Document.TransformViewModel.LockRotation}"
-                        CoverWholeScreen="{Binding Document.TransformViewModel.CoverWholeScreen}"
-                        SnapToAngles="{Binding Document.TransformViewModel.SnapToAngles}"
-                        InternalState="{Binding Document.TransformViewModel.InternalState, Mode=TwoWay}"
-                        ZoomboxScale="{Binding Zoombox.Scale}"
-                        ZoomboxAngle="{Binding Zoombox.Angle}"/>
-                    <lineOverlay:LineToolOverlay
-                        Focusable="False"
-                        Visibility="{Binding Document.LineToolOverlayViewModel.IsEnabled, Converter={converters:BoolToVisibilityConverter}}"
-                        ActionCompleted="{Binding Document.LineToolOverlayViewModel.ActionCompletedCommand}"
-                        LineStart="{Binding Document.LineToolOverlayViewModel.LineStart, Mode=TwoWay}"
-                        LineEnd="{Binding Document.LineToolOverlayViewModel.LineEnd, Mode=TwoWay}"
-                        ZoomboxScale="{Binding Zoombox.Scale}"/>
+                    <Grid ZIndex="5">
+                        <symOverlay:SymmetryOverlay
+                            Focusable="False"
+                            IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"
+                            ZoomboxScale="{Binding Zoombox.Scale}"
+                            HorizontalAxisVisible="{Binding Document.HorizontalSymmetryAxisEnabledBindable}"
+                            VerticalAxisVisible="{Binding Document.VerticalSymmetryAxisEnabledBindable}"
+                            HorizontalAxisY="{Binding Document.HorizontalSymmetryAxisYBindable, Mode=OneWay}"
+                            VerticalAxisX="{Binding Document.VerticalSymmetryAxisXBindable, Mode=OneWay}"
+                            DragCommand="{cmds:Command PixiEditor.Document.DragSymmetry, UseProvided=True}"
+                            DragEndCommand="{cmds:Command PixiEditor.Document.EndDragSymmetry, UseProvided=True}"
+                            DragStartCommand="{cmds:Command PixiEditor.Document.StartDragSymmetry, UseProvided=True}" />
+                        <overlays:SelectionOverlay
+                            Focusable="False"
+                            ShowFill="{Binding ToolsSubViewModel.ActiveTool, Source={vm:MainVM}, Converter={converters:IsSelectionToolConverter}}"
+                            Path="{Binding Document.SelectionPathBindable}"
+                            ZoomboxScale="{Binding Zoombox.Scale}" />
+                        <brushOverlay:BrushShapeOverlay
+                            Focusable="False"
+                            IsHitTestVisible="False"
+                            Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={converters:InverseBoolToVisibilityConverter}}"
+                            ZoomboxScale="{Binding Zoombox.Scale}"
+                            MouseEventSource="{Binding Zoombox.Tag.BackgroundGrid, Mode=OneTime}"
+                            MouseReference="{Binding Zoombox.Tag.MainImage, Mode=OneTime}"
+                            BrushSize="{Binding ToolsSubViewModel.ActiveBasicToolbar.ToolSize, Source={vm:MainVM}}"
+                            BrushShape="{Binding ToolsSubViewModel.ActiveTool.BrushShape, Source={vm:MainVM}, FallbackValue={x:Static brushOverlay:BrushShape.Hidden}}" />
+                        <transformOverlay:TransformOverlay
+                            Focusable="False"
+                            Cursor="Arrow"
+                            IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"
+                            HorizontalAlignment="Stretch"
+                            VerticalAlignment="Stretch"
+                            Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={converters:BoolToVisibilityConverter}}"
+                            ActionCompleted="{Binding Document.TransformViewModel.ActionCompletedCommand}"
+                            Corners="{Binding Document.TransformViewModel.Corners, Mode=TwoWay}"
+                            RequestedCorners="{Binding Document.TransformViewModel.RequestedCorners, Mode=TwoWay}"
+                            CornerFreedom="{Binding Document.TransformViewModel.CornerFreedom}"
+                            SideFreedom="{Binding Document.TransformViewModel.SideFreedom}"
+                            LockRotation="{Binding Document.TransformViewModel.LockRotation}"
+                            CoverWholeScreen="{Binding Document.TransformViewModel.CoverWholeScreen}"
+                            SnapToAngles="{Binding Document.TransformViewModel.SnapToAngles}"
+                            InternalState="{Binding Document.TransformViewModel.InternalState, Mode=TwoWay}"
+                            ZoomboxScale="{Binding Zoombox.Scale}"
+                            ZoomboxAngle="{Binding Zoombox.Angle}" />
+                        <lineOverlay:LineToolOverlay
+                            Focusable="False"
+                            Visibility="{Binding Document.LineToolOverlayViewModel.IsEnabled, Converter={converters:BoolToVisibilityConverter}}"
+                            ActionCompleted="{Binding Document.LineToolOverlayViewModel.ActionCompletedCommand}"
+                            LineStart="{Binding Document.LineToolOverlayViewModel.LineStart, Mode=TwoWay}"
+                            LineEnd="{Binding Document.LineToolOverlayViewModel.LineEnd, Mode=TwoWay}"
+                            ZoomboxScale="{Binding Zoombox.Scale}" />
+                    </Grid>
                     <Grid IsHitTestVisible="False" 
                         ShowGridLines="True" Width="{Binding Document.Width}" Height="{Binding Document.Height}" Panel.ZIndex="10" 
                         Visibility="{Binding GridLinesVisible, Converter={converters:BoolToVisibilityConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}}">

+ 4 - 3
src/PixiEditorGen/PixiEditorGen.csproj

@@ -6,13 +6,14 @@
     <Nullable>enable</Nullable>
     <ImplicitUsings>enable</ImplicitUsings>
     <LangVersion>latest</LangVersion>
+    <Configurations>Debug;Release;Steam</Configurations>
+    <Platforms>AnyCPU;x64;x86</Platforms>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0"/>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
   </ItemGroup>
   <ItemGroup>
-    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true"
-          PackagePath="analyzers/dotnet/cs" Visible="false"/>
+    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
   </ItemGroup>
 </Project>

+ 2 - 0
src/PixiEditorTests/PixiEditorTests.csproj

@@ -8,6 +8,8 @@
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
 
     <Platforms>AnyCPU;x64;x86</Platforms>
+
+    <Configurations>Debug;Release;Steam</Configurations>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">