Browse Source

Merge pull request #971 from PixiEditor/fixes/17.06

Fixes/17.06
Krzysztof Krysiński 2 months ago
parent
commit
bec2f57cf9
22 changed files with 304 additions and 604 deletions
  1. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/PasteImage_UpdateableChange.cs
  2. 69 1
      src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs
  3. 0 256
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.deps.json
  4. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  5. 0 244
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.deps.json
  6. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll
  7. 2 1
      src/PixiEditor/Data/Localization/Languages/en.json
  8. 26 0
      src/PixiEditor/Helpers/Converters/AnyTrueConverter.cs
  9. 2 0
      src/PixiEditor/Models/Controllers/ClipboardController.cs
  10. 2 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PasteImageExecutor.cs
  11. 5 5
      src/PixiEditor/ViewModels/Document/DocumentManagerViewModel.cs
  12. 6 2
      src/PixiEditor/ViewModels/SubViewModels/ClipboardViewModel.cs
  13. 1 1
      src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs
  14. 5 5
      src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs
  15. 4 0
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  16. 4 4
      src/PixiEditor/Views/Layers/ReferenceLayer.axaml
  17. 17 2
      src/PixiEditor/Views/Main/DocumentPreview.axaml
  18. 23 53
      src/PixiEditor/Views/Main/Tools/ToolsPicker.axaml
  19. 1 0
      src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml
  20. 27 0
      src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml.cs
  21. 62 18
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  22. 47 11
      src/PixiEditor/Views/Visuals/PreviewPainterControl.cs

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/PasteImage_UpdateableChange.cs

@@ -4,7 +4,7 @@ using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
-internal class PasteImage_UpdateableChange : UpdateableChange
+internal class PasteImage_UpdateableChange : InterruptableUpdateableChange
 {
 {
     private ShapeCorners corners;
     private ShapeCorners corners;
     private readonly Guid memberGuid;
     private readonly Guid memberGuid;

+ 69 - 1
src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs

@@ -16,6 +16,8 @@ internal sealed class RotateImage_Change : Change
     private readonly RotationAngle rotation;
     private readonly RotationAngle rotation;
     private List<Guid> membersToRotate;
     private List<Guid> membersToRotate;
 
 
+    private Dictionary<Guid, Matrix3X3> originalTransformations = new();
+
     private VecI originalSize;
     private VecI originalSize;
     private double originalHorAxisY;
     private double originalHorAxisY;
     private double originalVerAxisX;
     private double originalVerAxisX;
@@ -28,6 +30,7 @@ internal sealed class RotateImage_Change : Change
     {
     {
         this.rotation = rotation;
         this.rotation = rotation;
         membersToRotate ??= new List<Guid>();
         membersToRotate ??= new List<Guid>();
+        originalTransformations = new Dictionary<Guid, Matrix3X3>();
         this.membersToRotate = membersToRotate;
         this.membersToRotate = membersToRotate;
         this.frame = frame < 0 ? null : frame;
         this.frame = frame < 0 ? null : frame;
     }
     }
@@ -143,6 +146,7 @@ internal sealed class RotateImage_Change : Change
 
 
     private OneOf<None, IChangeInfo, List<IChangeInfo>> Rotate(Document target)
     private OneOf<None, IChangeInfo, List<IChangeInfo>> Rotate(Document target)
     {
     {
+        originalTransformations.Clear();
         if (membersToRotate.Count == 0)
         if (membersToRotate.Count == 0)
         {
         {
             return RotateWholeImage(target);
             return RotateWholeImage(target);
@@ -173,7 +177,13 @@ internal sealed class RotateImage_Change : Change
                 }
                 }
                 else if (member is ITransformableObject transformableObject)
                 else if (member is ITransformableObject transformableObject)
                 {
                 {
-                    RectD? tightBounds = member.GetTightBounds(frame.Value);
+                    RectD? tightBounds = member.GetTightBounds(frame ?? 0);
+
+                    if (tightBounds is null)
+                        return;
+
+                    originalTransformations[member.Id] = transformableObject.TransformationMatrix;
+
                     transformableObject.TransformationMatrix = transformableObject.TransformationMatrix.PostConcat(
                     transformableObject.TransformationMatrix = transformableObject.TransformationMatrix.PostConcat(
                         Matrix3X3.CreateRotation(
                         Matrix3X3.CreateRotation(
                             RotationAngleToRadians(rotation),
                             RotationAngleToRadians(rotation),
@@ -195,8 +205,12 @@ internal sealed class RotateImage_Change : Change
         int newWidth = rotation == RotationAngle.D180 ? target.Size.X : target.Size.Y;
         int newWidth = rotation == RotationAngle.D180 ? target.Size.X : target.Size.Y;
         int newHeight = rotation == RotationAngle.D180 ? target.Size.Y : target.Size.X;
         int newHeight = rotation == RotationAngle.D180 ? target.Size.Y : target.Size.X;
 
 
+        VecI oldSize = target.Size;
         VecI newSize = new VecI(newWidth, newHeight);
         VecI newSize = new VecI(newWidth, newHeight);
 
 
+        VecD imageCenterOld = new VecD(oldSize.X / 2f, oldSize.Y / 2f);
+        VecD imageCenterNew = new VecD(newSize.X / 2f, newSize.Y / 2f);
+
         double normalizedSymmX = originalVerAxisX / Math.Max(target.Size.X, 0.1f);
         double normalizedSymmX = originalVerAxisX / Math.Max(target.Size.X, 0.1f);
         double normalizedSymmY = originalHorAxisY / Math.Max(target.Size.Y, 0.1f);
         double normalizedSymmY = originalHorAxisY / Math.Max(target.Size.Y, 0.1f);
 
 
@@ -220,6 +234,43 @@ internal sealed class RotateImage_Change : Change
                     });
                     });
                 }
                 }
             }
             }
+            else
+            {
+                if (member is ITransformableObject transformableObject)
+                {
+                    RectD? tightBounds = member.GetTightBounds(0);
+                    if (tightBounds is null)
+                        return;
+
+                    originalTransformations[member.Id] = transformableObject.TransformationMatrix;
+
+                    float radians = RotationAngleToRadians(rotation);
+
+                    VecD objectCenter = new VecD((float)tightBounds.Value.Center.X, (float)tightBounds.Value.Center.Y);
+
+                    var rotationMatrix =
+                        Matrix3X3.CreateRotation(radians, (float)objectCenter.X, (float)objectCenter.Y);
+
+                    VecD offsetFromCenter = objectCenter - imageCenterOld;
+
+                    VecD rotatedOffset = rotation switch
+                    {
+                        RotationAngle.D90 => new VecD(-offsetFromCenter.Y, offsetFromCenter.X),
+                        RotationAngle.D180 => -offsetFromCenter,
+                        RotationAngle.D270 => new VecD(offsetFromCenter.Y, -offsetFromCenter.X),
+                        _ => offsetFromCenter
+                    };
+
+                    VecD newObjectCenter = imageCenterNew + rotatedOffset;
+
+                    VecD delta = newObjectCenter - objectCenter;
+                    var translationMatrix = Matrix3X3.CreateTranslation(delta.X, delta.Y);
+
+                    transformableObject.TransformationMatrix =
+                        transformableObject.TransformationMatrix.PostConcat(rotationMatrix)
+                            .PostConcat(translationMatrix);
+                }
+            }
 
 
             if (member.EmbeddedMask is null)
             if (member.EmbeddedMask is null)
                 return;
                 return;
@@ -232,6 +283,23 @@ internal sealed class RotateImage_Change : Change
 
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
     {
+        if (originalTransformations.Count > 0)
+        {
+            foreach (var item in originalTransformations)
+            {
+                var member = item.Key;
+
+                if (!target.HasMember(member))
+                    continue;
+
+                var memberNode = target.FindMember(member);
+                if (memberNode is ITransformableObject transformableObject)
+                {
+                    transformableObject.TransformationMatrix = item.Value;
+                }
+            }
+        }
+
         if (membersToRotate.Count == 0)
         if (membersToRotate.Count == 0)
         {
         {
             return RevertRotateWholeImage(target);
             return RevertRotateWholeImage(target);

+ 0 - 256
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.deps.json

@@ -1,256 +0,0 @@
-{
-  "runtimeTarget": {
-    "name": ".NETStandard,Version=v2.0/",
-    "signature": ""
-  },
-  "compilationOptions": {},
-  "targets": {
-    ".NETStandard,Version=v2.0": {},
-    ".NETStandard,Version=v2.0/": {
-      "PixiEditor.Api.CGlueMSBuild/1.0.0": {
-        "dependencies": {
-          "Microsoft.Build.Utilities.Core": "17.12.6",
-          "Mono.Cecil": "0.11.6",
-          "NETStandard.Library": "2.0.3",
-          "StyleCop.Analyzers": "1.1.118"
-        },
-        "runtime": {
-          "PixiEditor.Api.CGlueMSBuild.dll": {}
-        }
-      },
-      "Microsoft.Build.Framework/17.12.6": {
-        "dependencies": {
-          "Microsoft.Win32.Registry": "5.0.0",
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0",
-          "System.Security.Principal.Windows": "5.0.0"
-        }
-      },
-      "Microsoft.Build.Utilities.Core/17.12.6": {
-        "dependencies": {
-          "Microsoft.Build.Framework": "17.12.6",
-          "Microsoft.NET.StringTools": "17.12.6",
-          "Microsoft.Win32.Registry": "5.0.0",
-          "System.Collections.Immutable": "8.0.0",
-          "System.Configuration.ConfigurationManager": "8.0.0",
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0",
-          "System.Security.Principal.Windows": "5.0.0",
-          "System.Text.Encoding.CodePages": "7.0.0"
-        }
-      },
-      "Microsoft.NET.StringTools/17.12.6": {
-        "dependencies": {
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
-        }
-      },
-      "Microsoft.NETCore.Platforms/1.1.0": {},
-      "Microsoft.Win32.Registry/5.0.0": {
-        "dependencies": {
-          "System.Buffers": "4.5.1",
-          "System.Memory": "4.5.5",
-          "System.Security.AccessControl": "5.0.0",
-          "System.Security.Principal.Windows": "5.0.0"
-        }
-      },
-      "Mono.Cecil/0.11.6": {
-        "runtime": {
-          "lib/netstandard2.0/Mono.Cecil.Mdb.dll": {
-            "assemblyVersion": "0.11.6.0",
-            "fileVersion": "0.11.6.0"
-          },
-          "lib/netstandard2.0/Mono.Cecil.Pdb.dll": {
-            "assemblyVersion": "0.11.6.0",
-            "fileVersion": "0.11.6.0"
-          },
-          "lib/netstandard2.0/Mono.Cecil.Rocks.dll": {
-            "assemblyVersion": "0.11.6.0",
-            "fileVersion": "0.11.6.0"
-          },
-          "lib/netstandard2.0/Mono.Cecil.dll": {
-            "assemblyVersion": "0.11.6.0",
-            "fileVersion": "0.11.6.0"
-          }
-        }
-      },
-      "NETStandard.Library/2.0.3": {
-        "dependencies": {
-          "Microsoft.NETCore.Platforms": "1.1.0"
-        }
-      },
-      "StyleCop.Analyzers/1.1.118": {},
-      "System.Buffers/4.5.1": {},
-      "System.Collections.Immutable/8.0.0": {
-        "dependencies": {
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
-        }
-      },
-      "System.Configuration.ConfigurationManager/8.0.0": {
-        "dependencies": {
-          "System.Security.Cryptography.ProtectedData": "8.0.0"
-        }
-      },
-      "System.Memory/4.5.5": {
-        "dependencies": {
-          "System.Buffers": "4.5.1",
-          "System.Numerics.Vectors": "4.4.0",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
-        }
-      },
-      "System.Numerics.Vectors/4.4.0": {},
-      "System.Runtime.CompilerServices.Unsafe/6.0.0": {},
-      "System.Security.AccessControl/5.0.0": {
-        "dependencies": {
-          "System.Security.Principal.Windows": "5.0.0"
-        }
-      },
-      "System.Security.Cryptography.ProtectedData/8.0.0": {
-        "dependencies": {
-          "System.Memory": "4.5.5"
-        }
-      },
-      "System.Security.Principal.Windows/5.0.0": {},
-      "System.Text.Encoding.CodePages/7.0.0": {
-        "dependencies": {
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
-        }
-      }
-    }
-  },
-  "libraries": {
-    "PixiEditor.Api.CGlueMSBuild/1.0.0": {
-      "type": "project",
-      "serviceable": false,
-      "sha512": ""
-    },
-    "Microsoft.Build.Framework/17.12.6": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-jleteC0seumLGTmTVwob97lcwPj/dfgzL/V3g/VVcMZgo2Ic7jzdy8AYpByPDh8e3uRq0SjCl6HOFCjhy5GzRQ==",
-      "path": "microsoft.build.framework/17.12.6",
-      "hashPath": "microsoft.build.framework.17.12.6.nupkg.sha512"
-    },
-    "Microsoft.Build.Utilities.Core/17.12.6": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-pU3GnHcXp8VRMGKxdJCq+tixfhFn+QwEbpqmZmc/nqFHFyuhlGwjonWZMIWcwuCv/8EHgxoOttFvna1vrN+RrA==",
-      "path": "microsoft.build.utilities.core/17.12.6",
-      "hashPath": "microsoft.build.utilities.core.17.12.6.nupkg.sha512"
-    },
-    "Microsoft.NET.StringTools/17.12.6": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-w8Ehofqte5bJoR+Fa3f6JwkwFEkGtXxqvQHGOVOSHDzgNVySvL5FSNhavbQSZ864el9c3rjdLPLAtBW8dq6fmg==",
-      "path": "microsoft.net.stringtools/17.12.6",
-      "hashPath": "microsoft.net.stringtools.17.12.6.nupkg.sha512"
-    },
-    "Microsoft.NETCore.Platforms/1.1.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
-      "path": "microsoft.netcore.platforms/1.1.0",
-      "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
-    },
-    "Microsoft.Win32.Registry/5.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
-      "path": "microsoft.win32.registry/5.0.0",
-      "hashPath": "microsoft.win32.registry.5.0.0.nupkg.sha512"
-    },
-    "Mono.Cecil/0.11.6": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-f33RkDtZO8VlGXCtmQIviOtxgnUdym9xx/b1p9h91CRGOsJFxCFOFK1FDbVt1OCf1aWwYejUFa2MOQyFWTFjbA==",
-      "path": "mono.cecil/0.11.6",
-      "hashPath": "mono.cecil.0.11.6.nupkg.sha512"
-    },
-    "NETStandard.Library/2.0.3": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
-      "path": "netstandard.library/2.0.3",
-      "hashPath": "netstandard.library.2.0.3.nupkg.sha512"
-    },
-    "StyleCop.Analyzers/1.1.118": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==",
-      "path": "stylecop.analyzers/1.1.118",
-      "hashPath": "stylecop.analyzers.1.1.118.nupkg.sha512"
-    },
-    "System.Buffers/4.5.1": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
-      "path": "system.buffers/4.5.1",
-      "hashPath": "system.buffers.4.5.1.nupkg.sha512"
-    },
-    "System.Collections.Immutable/8.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
-      "path": "system.collections.immutable/8.0.0",
-      "hashPath": "system.collections.immutable.8.0.0.nupkg.sha512"
-    },
-    "System.Configuration.ConfigurationManager/8.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-JlYi9XVvIREURRUlGMr1F6vOFLk7YSY4p1vHo4kX3tQ0AGrjqlRWHDi66ImHhy6qwXBG3BJ6Y1QlYQ+Qz6Xgww==",
-      "path": "system.configuration.configurationmanager/8.0.0",
-      "hashPath": "system.configuration.configurationmanager.8.0.0.nupkg.sha512"
-    },
-    "System.Memory/4.5.5": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
-      "path": "system.memory/4.5.5",
-      "hashPath": "system.memory.4.5.5.nupkg.sha512"
-    },
-    "System.Numerics.Vectors/4.4.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==",
-      "path": "system.numerics.vectors/4.4.0",
-      "hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512"
-    },
-    "System.Runtime.CompilerServices.Unsafe/6.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
-      "path": "system.runtime.compilerservices.unsafe/6.0.0",
-      "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
-    },
-    "System.Security.AccessControl/5.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==",
-      "path": "system.security.accesscontrol/5.0.0",
-      "hashPath": "system.security.accesscontrol.5.0.0.nupkg.sha512"
-    },
-    "System.Security.Cryptography.ProtectedData/8.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==",
-      "path": "system.security.cryptography.protecteddata/8.0.0",
-      "hashPath": "system.security.cryptography.protecteddata.8.0.0.nupkg.sha512"
-    },
-    "System.Security.Principal.Windows/5.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==",
-      "path": "system.security.principal.windows/5.0.0",
-      "hashPath": "system.security.principal.windows.5.0.0.nupkg.sha512"
-    },
-    "System.Text.Encoding.CodePages/7.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==",
-      "path": "system.text.encoding.codepages/7.0.0",
-      "hashPath": "system.text.encoding.codepages.7.0.0.nupkg.sha512"
-    }
-  }
-}

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


+ 0 - 244
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.deps.json

@@ -1,244 +0,0 @@
-{
-  "runtimeTarget": {
-    "name": ".NETStandard,Version=v2.0/",
-    "signature": ""
-  },
-  "compilationOptions": {},
-  "targets": {
-    ".NETStandard,Version=v2.0": {},
-    ".NETStandard,Version=v2.0/": {
-      "PixiEditor.Extensions.MSPackageBuilder/1.0.0": {
-        "dependencies": {
-          "Microsoft.Build.Utilities.Core": "17.12.6",
-          "NETStandard.Library": "2.0.3",
-          "Newtonsoft.Json": "13.0.3",
-          "StyleCop.Analyzers": "1.1.118"
-        },
-        "runtime": {
-          "PixiEditor.Extensions.MSPackageBuilder.dll": {}
-        }
-      },
-      "Microsoft.Build.Framework/17.12.6": {
-        "dependencies": {
-          "Microsoft.Win32.Registry": "5.0.0",
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0",
-          "System.Security.Principal.Windows": "5.0.0"
-        }
-      },
-      "Microsoft.Build.Utilities.Core/17.12.6": {
-        "dependencies": {
-          "Microsoft.Build.Framework": "17.12.6",
-          "Microsoft.NET.StringTools": "17.12.6",
-          "Microsoft.Win32.Registry": "5.0.0",
-          "System.Collections.Immutable": "8.0.0",
-          "System.Configuration.ConfigurationManager": "8.0.0",
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0",
-          "System.Security.Principal.Windows": "5.0.0",
-          "System.Text.Encoding.CodePages": "7.0.0"
-        }
-      },
-      "Microsoft.NET.StringTools/17.12.6": {
-        "dependencies": {
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
-        }
-      },
-      "Microsoft.NETCore.Platforms/1.1.0": {},
-      "Microsoft.Win32.Registry/5.0.0": {
-        "dependencies": {
-          "System.Buffers": "4.5.1",
-          "System.Memory": "4.5.5",
-          "System.Security.AccessControl": "5.0.0",
-          "System.Security.Principal.Windows": "5.0.0"
-        }
-      },
-      "NETStandard.Library/2.0.3": {
-        "dependencies": {
-          "Microsoft.NETCore.Platforms": "1.1.0"
-        }
-      },
-      "Newtonsoft.Json/13.0.3": {
-        "runtime": {
-          "lib/netstandard2.0/Newtonsoft.Json.dll": {
-            "assemblyVersion": "13.0.0.0",
-            "fileVersion": "13.0.3.27908"
-          }
-        }
-      },
-      "StyleCop.Analyzers/1.1.118": {},
-      "System.Buffers/4.5.1": {},
-      "System.Collections.Immutable/8.0.0": {
-        "dependencies": {
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
-        }
-      },
-      "System.Configuration.ConfigurationManager/8.0.0": {
-        "dependencies": {
-          "System.Security.Cryptography.ProtectedData": "8.0.0"
-        }
-      },
-      "System.Memory/4.5.5": {
-        "dependencies": {
-          "System.Buffers": "4.5.1",
-          "System.Numerics.Vectors": "4.4.0",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
-        }
-      },
-      "System.Numerics.Vectors/4.4.0": {},
-      "System.Runtime.CompilerServices.Unsafe/6.0.0": {},
-      "System.Security.AccessControl/5.0.0": {
-        "dependencies": {
-          "System.Security.Principal.Windows": "5.0.0"
-        }
-      },
-      "System.Security.Cryptography.ProtectedData/8.0.0": {
-        "dependencies": {
-          "System.Memory": "4.5.5"
-        }
-      },
-      "System.Security.Principal.Windows/5.0.0": {},
-      "System.Text.Encoding.CodePages/7.0.0": {
-        "dependencies": {
-          "System.Memory": "4.5.5",
-          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
-        }
-      }
-    }
-  },
-  "libraries": {
-    "PixiEditor.Extensions.MSPackageBuilder/1.0.0": {
-      "type": "project",
-      "serviceable": false,
-      "sha512": ""
-    },
-    "Microsoft.Build.Framework/17.12.6": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-jleteC0seumLGTmTVwob97lcwPj/dfgzL/V3g/VVcMZgo2Ic7jzdy8AYpByPDh8e3uRq0SjCl6HOFCjhy5GzRQ==",
-      "path": "microsoft.build.framework/17.12.6",
-      "hashPath": "microsoft.build.framework.17.12.6.nupkg.sha512"
-    },
-    "Microsoft.Build.Utilities.Core/17.12.6": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-pU3GnHcXp8VRMGKxdJCq+tixfhFn+QwEbpqmZmc/nqFHFyuhlGwjonWZMIWcwuCv/8EHgxoOttFvna1vrN+RrA==",
-      "path": "microsoft.build.utilities.core/17.12.6",
-      "hashPath": "microsoft.build.utilities.core.17.12.6.nupkg.sha512"
-    },
-    "Microsoft.NET.StringTools/17.12.6": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-w8Ehofqte5bJoR+Fa3f6JwkwFEkGtXxqvQHGOVOSHDzgNVySvL5FSNhavbQSZ864el9c3rjdLPLAtBW8dq6fmg==",
-      "path": "microsoft.net.stringtools/17.12.6",
-      "hashPath": "microsoft.net.stringtools.17.12.6.nupkg.sha512"
-    },
-    "Microsoft.NETCore.Platforms/1.1.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
-      "path": "microsoft.netcore.platforms/1.1.0",
-      "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
-    },
-    "Microsoft.Win32.Registry/5.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
-      "path": "microsoft.win32.registry/5.0.0",
-      "hashPath": "microsoft.win32.registry.5.0.0.nupkg.sha512"
-    },
-    "NETStandard.Library/2.0.3": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
-      "path": "netstandard.library/2.0.3",
-      "hashPath": "netstandard.library.2.0.3.nupkg.sha512"
-    },
-    "Newtonsoft.Json/13.0.3": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
-      "path": "newtonsoft.json/13.0.3",
-      "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
-    },
-    "StyleCop.Analyzers/1.1.118": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==",
-      "path": "stylecop.analyzers/1.1.118",
-      "hashPath": "stylecop.analyzers.1.1.118.nupkg.sha512"
-    },
-    "System.Buffers/4.5.1": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
-      "path": "system.buffers/4.5.1",
-      "hashPath": "system.buffers.4.5.1.nupkg.sha512"
-    },
-    "System.Collections.Immutable/8.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
-      "path": "system.collections.immutable/8.0.0",
-      "hashPath": "system.collections.immutable.8.0.0.nupkg.sha512"
-    },
-    "System.Configuration.ConfigurationManager/8.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-JlYi9XVvIREURRUlGMr1F6vOFLk7YSY4p1vHo4kX3tQ0AGrjqlRWHDi66ImHhy6qwXBG3BJ6Y1QlYQ+Qz6Xgww==",
-      "path": "system.configuration.configurationmanager/8.0.0",
-      "hashPath": "system.configuration.configurationmanager.8.0.0.nupkg.sha512"
-    },
-    "System.Memory/4.5.5": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
-      "path": "system.memory/4.5.5",
-      "hashPath": "system.memory.4.5.5.nupkg.sha512"
-    },
-    "System.Numerics.Vectors/4.4.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==",
-      "path": "system.numerics.vectors/4.4.0",
-      "hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512"
-    },
-    "System.Runtime.CompilerServices.Unsafe/6.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
-      "path": "system.runtime.compilerservices.unsafe/6.0.0",
-      "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
-    },
-    "System.Security.AccessControl/5.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==",
-      "path": "system.security.accesscontrol/5.0.0",
-      "hashPath": "system.security.accesscontrol.5.0.0.nupkg.sha512"
-    },
-    "System.Security.Cryptography.ProtectedData/8.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==",
-      "path": "system.security.cryptography.protecteddata/8.0.0",
-      "hashPath": "system.security.cryptography.protecteddata.8.0.0.nupkg.sha512"
-    },
-    "System.Security.Principal.Windows/5.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==",
-      "path": "system.security.principal.windows/5.0.0",
-      "hashPath": "system.security.principal.windows.5.0.0.nupkg.sha512"
-    },
-    "System.Text.Encoding.CodePages/7.0.0": {
-      "type": "package",
-      "serviceable": true,
-      "sha512": "sha512-LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==",
-      "path": "system.text.encoding.codepages/7.0.0",
-      "hashPath": "system.text.encoding.codepages.7.0.0.nupkg.sha512"
-    }
-  }
-}

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll


+ 2 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1075,5 +1075,6 @@
   "NEW_MIN": "New Min",
   "NEW_MIN": "New Min",
   "NEW_MAX": "New Max",
   "NEW_MAX": "New Max",
   "REMAP_NODE": "Remap",
   "REMAP_NODE": "Remap",
-  "TEXT_TOOL_ACTION_DISPLAY": "Click on the canvas to add a new text (drag while clicking to set the size). Click on existing text to edit it."
+  "TEXT_TOOL_ACTION_DISPLAY": "Click on the canvas to add a new text (drag while clicking to set the size). Click on existing text to edit it.",
+  "PASTE_CELS": "Paste cels"
 }
 }

+ 26 - 0
src/PixiEditor/Helpers/Converters/AnyTrueConverter.cs

@@ -0,0 +1,26 @@
+using System.Globalization;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class AnyTrueConverter : SingleInstanceMultiValueConverter<AnyTrueConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is IEnumerable<bool> bools)
+        {
+            return bools.Any(x => x);
+        }
+
+        return false;
+    }
+
+    public override object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (values.All(x => x is bool b))
+        {
+            return values.Cast<bool>().Any(x => x);
+        }
+
+        return false;
+    }
+}

+ 2 - 0
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -215,6 +215,8 @@ internal static class ClipboardController
                 }
                 }
             }
             }
 
 
+            manager.Owner.ToolsSubViewModel.SetActiveTool<MoveToolViewModel>(false);
+
             return true;
             return true;
         }
         }
 
 

+ 2 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PasteImageExecutor.cs

@@ -17,6 +17,8 @@ internal class PasteImageExecutor : UpdateableChangeExecutor, ITransformableExec
     private bool drawOnMask;
     private bool drawOnMask;
     private Guid? memberGuid;
     private Guid? memberGuid;
 
 
+    public override bool BlocksOtherActions => false;
+
     public PasteImageExecutor(Surface image, VecI pos)
     public PasteImageExecutor(Surface image, VecI pos)
     {
     {
         this.image = image;
         this.image = image;

+ 5 - 5
src/PixiEditor/ViewModels/Document/DocumentManagerViewModel.cs

@@ -92,11 +92,11 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
 
 
     [Command.Basic("PixiEditor.Document.FlipLayersHorizontal", FlipType.Horizontal, "FLIP_LAYERS_HORIZONTALLY",
     [Command.Basic("PixiEditor.Document.FlipLayersHorizontal", FlipType.Horizontal, "FLIP_LAYERS_HORIZONTALLY",
         "FLIP_LAYERS_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
         "FLIP_LAYERS_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
-        MenuItemPath = "IMAGE/FLIP/FLIP_LAYERS_HORIZONTALLY", MenuItemOrder = 16, Icon = PixiPerfectIcons.MirrorHorizontal,
+        MenuItemPath = "LAYER/FLIP/FLIP_LAYERS_HORIZONTALLY", MenuItemOrder = 16, Icon = PixiPerfectIcons.MirrorHorizontal,
         AnalyticsTrack = true)]
         AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.FlipLayersVertical", FlipType.Vertical, "FLIP_LAYERS_VERTICALLY",
     [Command.Basic("PixiEditor.Document.FlipLayersVertical", FlipType.Vertical, "FLIP_LAYERS_VERTICALLY",
         "FLIP_LAYERS_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
         "FLIP_LAYERS_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
-        MenuItemPath = "IMAGE/FLIP/FLIP_LAYERS_VERTICALLY", MenuItemOrder = 17, Icon = PixiPerfectIcons.MirrorVertical,
+        MenuItemPath = "LAYER/FLIP/FLIP_LAYERS_VERTICALLY", MenuItemOrder = 17, Icon = PixiPerfectIcons.MirrorVertical,
         AnalyticsTrack = true)]
         AnalyticsTrack = true)]
     public void FlipLayers(FlipType type)
     public void FlipLayers(FlipType type)
     {
     {
@@ -123,15 +123,15 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
 
 
     [Command.Basic("PixiEditor.Document.Rotate90DegLayers", "ROT_LAYERS_90",
     [Command.Basic("PixiEditor.Document.Rotate90DegLayers", "ROT_LAYERS_90",
         "ROT_LAYERS_90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D90,
         "ROT_LAYERS_90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D90,
-        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_90_D", MenuItemOrder = 11, Icon = PixiPerfectIcons.File90,
+        MenuItemPath = "LAYER/ROTATION/ROT_LAYERS_90_D", MenuItemOrder = 11, Icon = PixiPerfectIcons.File90,
         AnalyticsTrack = true)]
         AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.Rotate180DegLayers", "ROT_LAYERS_180",
     [Command.Basic("PixiEditor.Document.Rotate180DegLayers", "ROT_LAYERS_180",
         "ROT_LAYERS_180", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D180,
         "ROT_LAYERS_180", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D180,
-        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_180_D", MenuItemOrder = 12, Icon = PixiPerfectIcons.File180,
+        MenuItemPath = "LAYER/ROTATION/ROT_LAYERS_180_D", MenuItemOrder = 12, Icon = PixiPerfectIcons.File180,
         AnalyticsTrack = true)]
         AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.Rotate270DegLayers", "ROT_LAYERS_-90",
     [Command.Basic("PixiEditor.Document.Rotate270DegLayers", "ROT_LAYERS_-90",
         "ROT_LAYERS_-90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D270,
         "ROT_LAYERS_-90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D270,
-        MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_-90_D", MenuItemOrder = 13, Icon = PixiPerfectIcons.FileMinus90,
+        MenuItemPath = "LAYER/ROTATION/ROT_LAYERS_-90_D", MenuItemOrder = 13, Icon = PixiPerfectIcons.FileMinus90,
         AnalyticsTrack = true)]
         AnalyticsTrack = true)]
     public void RotateLayers(RotationAngle angle)
     public void RotateLayers(RotationAngle angle)
     {
     {

+ 6 - 2
src/PixiEditor/ViewModels/SubViewModels/ClipboardViewModel.cs

@@ -87,7 +87,11 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
             Guid[] guids = doc.StructureHelper.GetAllLayers().Select(x => x.Id).ToArray();
             Guid[] guids = doc.StructureHelper.GetAllLayers().Select(x => x.Id).ToArray();
             await ClipboardController.TryPasteFromClipboard(doc, Owner.DocumentManagerSubViewModel, pasteAsNewLayer);
             await ClipboardController.TryPasteFromClipboard(doc, Owner.DocumentManagerSubViewModel, pasteAsNewLayer);
 
 
-            doc.Operations.InvokeCustomAction(() =>
+            // Leaving the code below commented out in case something breaks.
+            // It instantly ended paste image operation after I made it interruptable,
+            // I did test it, and it seems everything works fine without it.
+            /*doc.Operations.InvokeCustomAction(
+                () =>
             {
             {
                 Guid[] newGuids = doc.StructureHelper.GetAllLayers().Select(x => x.Id).ToArray();
                 Guid[] newGuids = doc.StructureHelper.GetAllLayers().Select(x => x.Id).ToArray();
 
 
@@ -102,7 +106,7 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
                         doc.Operations.AddSoftSelectedMember(diff[i]);
                         doc.Operations.AddSoftSelectedMember(diff[i]);
                     }
                     }
                 }
                 }
-            });
+            }, false);*/
         });
         });
     }
     }
 
 

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -612,7 +612,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
                     {
                     {
                         Dispatcher.UIThread.Post(() =>
                         Dispatcher.UIThread.Post(() =>
                         {
                         {
-                            if (IPreferences.Current.GetPreference<bool>(PreferencesConstants.OpenDirectoryOnExport))
+                            if (IPreferences.Current.GetPreference<bool>(PreferencesConstants.OpenDirectoryOnExport, true))
                             {
                             {
                                 IOperatingSystem.Current.OpenFolder(result.finalPath);
                                 IOperatingSystem.Current.OpenFolder(result.finalPath);
                             }
                             }

+ 5 - 5
src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs

@@ -614,7 +614,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
 
 
     [Command.Basic("PixiEditor.Layer.Rasterize", "RASTERIZE_ACTIVE_LAYER", "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE",
     [Command.Basic("PixiEditor.Layer.Rasterize", "RASTERIZE_ACTIVE_LAYER", "RASTERIZE_ACTIVE_LAYER_DESCRIPTIVE",
         CanExecute = "PixiEditor.Layer.AnySelectedLayerIsRasterizable",
         CanExecute = "PixiEditor.Layer.AnySelectedLayerIsRasterizable",
-        Icon = PixiPerfectIcons.LowresCircle, MenuItemPath = "LAYER/VECTOR/RASTERIZE_ACTIVE_LAYER",
+        Icon = PixiPerfectIcons.LowresCircle, MenuItemPath = "LAYER/RASTERIZE_ACTIVE_LAYER",
         AnalyticsTrack = true)]
         AnalyticsTrack = true)]
     public void RasterizeActiveLayer()
     public void RasterizeActiveLayer()
     {
     {
@@ -635,7 +635,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
 
 
     [Command.Basic("PixiEditor.Layer.ConvertToCurve", "CONVERT_TO_CURVE", "CONVERT_TO_CURVE_DESCRIPTIVE",
     [Command.Basic("PixiEditor.Layer.ConvertToCurve", "CONVERT_TO_CURVE", "CONVERT_TO_CURVE_DESCRIPTIVE",
         CanExecute = "PixiEditor.Layer.AnySelectedMemberIsVectorLayer",
         CanExecute = "PixiEditor.Layer.AnySelectedMemberIsVectorLayer",
-        MenuItemPath = "LAYER/VECTOR/CONVERT_TO_CURVE", AnalyticsTrack = true)]
+        MenuItemPath = "LAYER/CONVERT_TO_CURVE", AnalyticsTrack = true)]
     public void ConvertActiveLayersToCurve()
     public void ConvertActiveLayersToCurve()
     {
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -655,7 +655,7 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
 
 
     [Command.Basic("PixiEditor.Layer.SeparateShapes", "SEPARATE_SHAPES", "SEPARATE_SHAPES_DESCRIPTIVE",
     [Command.Basic("PixiEditor.Layer.SeparateShapes", "SEPARATE_SHAPES", "SEPARATE_SHAPES_DESCRIPTIVE",
         CanExecute = "PixiEditor.Layer.AnySelectedMemberIsVectorLayer",
         CanExecute = "PixiEditor.Layer.AnySelectedMemberIsVectorLayer",
-        MenuItemPath = "LAYER/VECTOR/SEPARATE_SHAPES", AnalyticsTrack = true)]
+        MenuItemPath = "LAYER/SEPARATE_SHAPES", AnalyticsTrack = true)]
     public void SeparateShapes()
     public void SeparateShapes()
     {
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -678,14 +678,14 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         Key = Key.X, Modifiers = KeyModifiers.Control | KeyModifiers.Shift,
         Key = Key.X, Modifiers = KeyModifiers.Control | KeyModifiers.Shift,
         Parameter = false,
         Parameter = false,
         ShortcutContexts = [typeof(ViewportWindowViewModel), typeof(TextOverlay)],
         ShortcutContexts = [typeof(ViewportWindowViewModel), typeof(TextOverlay)],
-        MenuItemPath = "LAYER/TEXT/EXTRACT_SELECTED_TEXT", AnalyticsTrack = true)]
+        MenuItemPath = "LAYER/EXTRACT_SELECTED_TEXT", AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Layer.ExtractSelectedCharacters", "EXTRACT_SELECTED_CHARACTERS",
     [Command.Basic("PixiEditor.Layer.ExtractSelectedCharacters", "EXTRACT_SELECTED_CHARACTERS",
         "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE",
         "EXTRACT_SELECTED_CHARACTERS_DESCRIPTIVE",
         CanExecute = "PixiEditor.Layer.SelectedMemberIsSelectedText",
         CanExecute = "PixiEditor.Layer.SelectedMemberIsSelectedText",
         Key = Key.X, Modifiers = KeyModifiers.Control | KeyModifiers.Shift | KeyModifiers.Alt,
         Key = Key.X, Modifiers = KeyModifiers.Control | KeyModifiers.Shift | KeyModifiers.Alt,
         Parameter = true,
         Parameter = true,
         ShortcutContexts = [typeof(ViewportWindowViewModel), typeof(TextOverlay)],
         ShortcutContexts = [typeof(ViewportWindowViewModel), typeof(TextOverlay)],
-        MenuItemPath = "LAYER/TEXT/EXTRACT_SELECTED_CHARACTERS", AnalyticsTrack = true)]
+        MenuItemPath = "LAYER/EXTRACT_SELECTED_CHARACTERS", AnalyticsTrack = true)]
     public void ExtractSelectedText(bool extractEachCharacter)
     public void ExtractSelectedText(bool extractEachCharacter)
     {
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;

+ 4 - 0
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -98,6 +98,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
 
 
     public ObservableCollection<IToolSetHandler> AllToolSets { get; } = new();
     public ObservableCollection<IToolSetHandler> AllToolSets { get; } = new();
     public List<string> AllToolSetNames => AllToolSets.Select(x => x.Name).ToList();
     public List<string> AllToolSetNames => AllToolSets.Select(x => x.Name).ToList();
+    public List<IToolSetHandler> NonSelectedToolSets => AllToolSets.Where(x => x != ActiveToolSet).ToList();
 
 
     public event EventHandler<SelectedToolEventArgs>? SelectedToolChanged;
     public event EventHandler<SelectedToolEventArgs>? SelectedToolChanged;
 
 
@@ -160,6 +161,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         SetActiveToolSet(AllToolSets.First());
         SetActiveToolSet(AllToolSets.First());
     }
     }
 
 
+    [Command.Internal("PixiEditor.Tools.SetActiveToolSet", AnalyticsTrack = true)]
     public void SetActiveToolSet(IToolSetHandler toolSetHandler)
     public void SetActiveToolSet(IToolSetHandler toolSetHandler)
     {
     {
         ActiveTool?.OnToolDeselected(false);
         ActiveTool?.OnToolDeselected(false);
@@ -168,6 +170,8 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         UpdateEnabledState();
         UpdateEnabledState();
 
 
         ActiveTool?.OnToolSelected(false);
         ActiveTool?.OnToolSelected(false);
+
+        OnPropertyChanged(nameof(NonSelectedToolSets));
     }
     }
 
 
     public void SetupToolsTooltipShortcuts()
     public void SetupToolsTooltipShortcuts()

+ 4 - 4
src/PixiEditor/Views/Layers/ReferenceLayer.axaml

@@ -16,10 +16,11 @@
              mc:Ignorable="d"
              mc:Ignorable="d"
              d:DesignHeight="60" d:DesignWidth="350" VerticalAlignment="Center" x:Name="uc">
              d:DesignHeight="60" d:DesignWidth="350" VerticalAlignment="Center" x:Name="uc">
     <UserControl.Styles>
     <UserControl.Styles>
+        <Style Selector="Button#topmostBtn">
+            <Setter Property="Content" Value="{DynamicResource icon-layers-top}"></Setter>
+        </Style>
         <Style Selector=":topmost Button#topmostBtn">
         <Style Selector=":topmost Button#topmostBtn">
-            <Setter Property="RenderTransform">
-                <RotateTransform Angle="180" />
-            </Setter>
+            <Setter Property="Content" Value="{DynamicResource icon-layers-bottom}"/>
         </Style>
         </Style>
     </UserControl.Styles>
     </UserControl.Styles>
     <Border BorderThickness="0 2 0 0" MinWidth="60"
     <Border BorderThickness="0 2 0 0" MinWidth="60"
@@ -67,7 +68,6 @@
                                 Command="{cmds:Command PixiEditor.Layer.ToggleReferenceLayerTopMost}"
                                 Command="{cmds:Command PixiEditor.Layer.ToggleReferenceLayerTopMost}"
                                 Name="topmostBtn"
                                 Name="topmostBtn"
                                 Classes="pixi-icon"
                                 Classes="pixi-icon"
-                                Content="{DynamicResource icon-reference-layer}"
                                 ToolTip.Tip="{Binding Document.ReferenceLayerViewModel.IsTopMost, ElementName=uc, Converter={converters:BoolToValueConverter FalseValue='localized:PUT_REFERENCE_LAYER_ABOVE', TrueValue='localized:PUT_REFERENCE_LAYER_BELOW'}}"
                                 ToolTip.Tip="{Binding Document.ReferenceLayerViewModel.IsTopMost, ElementName=uc, Converter={converters:BoolToValueConverter FalseValue='localized:PUT_REFERENCE_LAYER_ABOVE', TrueValue='localized:PUT_REFERENCE_LAYER_BELOW'}}"
                                 RenderOptions.BitmapInterpolationMode="HighQuality"
                                 RenderOptions.BitmapInterpolationMode="HighQuality"
                                 Width="24" Height="24" HorizontalAlignment="Right"/>
                                 Width="24" Height="24" HorizontalAlignment="Right"/>

+ 17 - 2
src/PixiEditor/Views/Main/DocumentPreview.axaml

@@ -7,6 +7,7 @@
              xmlns:input="clr-namespace:PixiEditor.Views.Input"
              xmlns:input="clr-namespace:PixiEditor.Views.Input"
              xmlns:ui1="clr-namespace:PixiEditor.Helpers.UI"
              xmlns:ui1="clr-namespace:PixiEditor.Helpers.UI"
              xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
              xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             xmlns:localization="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              Name="uc"
              Name="uc"
              x:Class="PixiEditor.Views.Main.DocumentPreview">
              x:Class="PixiEditor.Views.Main.DocumentPreview">
@@ -21,9 +22,11 @@
               IsVisible="{Binding !!Document, ElementName=uc}"
               IsVisible="{Binding !!Document, ElementName=uc}"
               Margin="10" Background="Transparent"
               Margin="10" Background="Transparent"
               d:Width="8" d:Height="8">
               d:Width="8" d:Height="8">
+
             <viewportControls:FixedViewport
             <viewportControls:FixedViewport
                 Delayed="True"
                 Delayed="True"
                 x:Name="viewport"
                 x:Name="viewport"
+                RenderInDocSize="{Binding ElementName=highDpiButton, Path=IsChecked}"
                 Document="{Binding Document, ElementName=uc}"
                 Document="{Binding Document, ElementName=uc}"
                 Background="{Binding ActiveItem.Value, ElementName=backgroundButton}"/>
                 Background="{Binding ActiveItem.Value, ElementName=backgroundButton}"/>
         </Grid>
         </Grid>
@@ -54,8 +57,20 @@
             </TextBlock>
             </TextBlock>
         </StackPanel>
         </StackPanel>
         <Grid Grid.Row="2" HorizontalAlignment="Right" Margin="0,0,5,0" ui:RenderOptionsBindable.BitmapInterpolationMode="{Binding ElementName=backgroundButton, Path=ActiveItem.ScalingMode}">
         <Grid Grid.Row="2" HorizontalAlignment="Right" Margin="0,0,5,0" ui:RenderOptionsBindable.BitmapInterpolationMode="{Binding ElementName=backgroundButton, Path=ActiveItem.ScalingMode}">
-            <StackPanel Orientation="Horizontal">
-                <input:ListSwitchButton x:Name="formatButton" Margin="0,0,5,0" Height="20">
+            <StackPanel Spacing="5" Orientation="Horizontal">
+                <StackPanel.Styles>
+                    <Style Selector="ToggleButton#highDpiButton">
+                        <Setter Property="Content" Value="{DynamicResource icon-circle}"/>
+                    </Style>
+                    <Style Selector="ToggleButton#highDpiButton:checked">
+                        <Setter Property="Content" Value="{DynamicResource icon-lowres-circle}"/>
+                        <Setter Property="Background" Value="Transparent"/>
+                        <Setter Property="BorderThickness" Value="0"/>
+                    </Style>
+                </StackPanel.Styles>
+
+                <ToggleButton x:Name="highDpiButton" Classes="pixi-icon" localization:Translator.TooltipKey="TOGGLE_HIGH_RES_PREVIEW"/>
+                <input:ListSwitchButton x:Name="formatButton" Height="20">
                     <input:ListSwitchButton.Items>
                     <input:ListSwitchButton.Items>
                         <input:SwitchItemObservableCollection>
                         <input:SwitchItemObservableCollection>
                             <input:SwitchItem Content="RGBA" Background="{DynamicResource ThemeControlMidBrush}" Value="RGBA"/>
                             <input:SwitchItem Content="RGBA" Background="{DynamicResource ThemeControlMidBrush}" Value="RGBA"/>

+ 23 - 53
src/PixiEditor/Views/Main/Tools/ToolsPicker.axaml

@@ -10,58 +10,28 @@
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              ClipToBounds="False"
              ClipToBounds="False"
              x:Class="PixiEditor.Views.Main.Tools.ToolsPicker" Name="picker">
              x:Class="PixiEditor.Views.Main.Tools.ToolsPicker" Name="picker">
-    <Border CornerRadius="{DynamicResource ControlCornerRadius}"
-            BorderBrush="{DynamicResource ThemeBorderMidBrush}"
-            BorderThickness="{DynamicResource ThemeBorderThickness}"
-            Cursor="Arrow"
-            Width="48"
-            Background="{DynamicResource ThemeBackgroundBrush1}">
-        <Grid>
-            <Grid.RowDefinitions>
-                <RowDefinition Height="25" />
-                <RowDefinition Height="*" />
-            </Grid.RowDefinitions>
-            
-            <Border Grid.Row="0" Background="{DynamicResource ThemeBackgroundBrush2}">
-                <StackPanel Orientation="Horizontal">
-                    <Button Classes="pixi-icon" Content="{DynamicResource icon-chevron-left}"
-                            Command="{Binding SwitchToolSetCommand, ElementName=picker}">
-                        <Button.CommandParameter>
-                            <system:Boolean>False</system:Boolean>
-                        </Button.CommandParameter>
-                    </Button>
-                    <Button Classes="pixi-icon" Content="{DynamicResource icon-chevron-right}"
-                            Command="{Binding SwitchToolSetCommand, ElementName=picker}">
-                        <Button.CommandParameter>
-                            <system:Boolean>True</system:Boolean>
-                        </Button.CommandParameter>
-                    </Button>
-                    <!--<ComboBox ItemsSource="{Binding ElementName=picker, Path=ToolSets}"
-                              SelectedItem="{Binding ElementName=picker, Path=ToolSet}"
-                              SelectedIndex="0">
-                        <ComboBox.ItemTemplate>
-                            <DataTemplate>
-                                <TextBlock ui:Translator.Key="{Binding Name}" />
-                            </DataTemplate>
-                        </ComboBox.ItemTemplate>
-                    </ComboBox>-->
-                </StackPanel>
-            </Border>
-            <ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
-                <ItemsControl ItemsSource="{Binding ElementName=picker, Path=ToolSet.Tools}" Padding="0 2">
-                    <ItemsControl.ItemTemplate>
-                        <DataTemplate DataType="tools:ToolViewModel">
-                            <tools1:ToolPickerButton DataContext="{Binding}"
-                                                     IsSelected="{Binding IsActive}" />
-                        </DataTemplate>
-                    </ItemsControl.ItemTemplate>
-                    <ItemsControl.ItemsPanel>
-                        <ItemsPanelTemplate>
-                            <StackPanel Orientation="Vertical" />
-                        </ItemsPanelTemplate>
-                    </ItemsControl.ItemsPanel>
-                </ItemsControl>
-            </ScrollViewer>
-        </Grid>
+    <Border
+        ClipToBounds="True"
+        Cursor="Arrow" Width="48"
+        BorderBrush="{DynamicResource ThemeBorderMidBrush}"
+        CornerRadius="{DynamicResource ControlCornerRadius}"
+        Padding="0, 5"
+        BorderThickness="{DynamicResource ThemeBorderThickness}"
+        Background="{DynamicResource ThemeBackgroundBrush1}">
+        <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
+            <ItemsControl ItemsSource="{Binding ElementName=picker, Path=ToolSet.Tools}" Padding="0 2">
+                <ItemsControl.ItemTemplate>
+                    <DataTemplate DataType="tools:ToolViewModel">
+                        <tools1:ToolPickerButton DataContext="{Binding}"
+                                                 IsSelected="{Binding IsActive}" />
+                    </DataTemplate>
+                </ItemsControl.ItemTemplate>
+                <ItemsControl.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <StackPanel Orientation="Vertical" />
+                    </ItemsPanelTemplate>
+                </ItemsControl.ItemsPanel>
+            </ItemsControl>
+        </ScrollViewer>
     </Border>
     </Border>
 </UserControl>
 </UserControl>

+ 1 - 0
src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml

@@ -18,6 +18,7 @@
             x:Name="mainImage"
             x:Name="mainImage"
             Focusable="True"
             Focusable="True"
             PreviewPainter="{Binding Document.PreviewPainter, ElementName=uc}"
             PreviewPainter="{Binding Document.PreviewPainter, ElementName=uc}"
+            CustomRenderSize="{Binding CustomRenderSize, ElementName=uc}"
             FrameToRender="{Binding Document.AnimationDataViewModel.ActiveFrameBindable, ElementName=uc}"
             FrameToRender="{Binding Document.AnimationDataViewModel.ActiveFrameBindable, ElementName=uc}"
             SizeChanged="OnImageSizeChanged">
             SizeChanged="OnImageSizeChanged">
             <ui1:RenderOptionsBindable.BitmapInterpolationMode>
             <ui1:RenderOptionsBindable.BitmapInterpolationMode>

+ 27 - 0
src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml.cs

@@ -23,6 +23,24 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
     public static readonly StyledProperty<bool> DelayedProperty =
     public static readonly StyledProperty<bool> DelayedProperty =
         AvaloniaProperty.Register<FixedViewport, bool>(nameof(Delayed), false);
         AvaloniaProperty.Register<FixedViewport, bool>(nameof(Delayed), false);
 
 
+    public static readonly StyledProperty<bool> RenderInDocSizeProperty = AvaloniaProperty.Register<FixedViewport, bool>(
+        nameof(RenderInDocSize));
+
+    public static readonly StyledProperty<VecI> CustomRenderSizeProperty = AvaloniaProperty.Register<FixedViewport, VecI>(
+        nameof(CustomRenderSize));
+
+    public VecI CustomRenderSize
+    {
+        get => GetValue(CustomRenderSizeProperty);
+        set => SetValue(CustomRenderSizeProperty, value);
+    }
+
+    public bool RenderInDocSize
+    {
+        get => GetValue(RenderInDocSizeProperty);
+        set => SetValue(RenderInDocSizeProperty, value);
+    }
+
     public event PropertyChangedEventHandler? PropertyChanged;
     public event PropertyChangedEventHandler? PropertyChanged;
 
 
     public bool Delayed
     public bool Delayed
@@ -42,6 +60,7 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
     static FixedViewport()
     static FixedViewport()
     {
     {
         DocumentProperty.Changed.Subscribe(OnDocumentChange);
         DocumentProperty.Changed.Subscribe(OnDocumentChange);
+        RenderInDocSizeProperty.Changed.Subscribe(OnRenderInDocSizeChanged);
     }
     }
 
 
     public FixedViewport()
     public FixedViewport()
@@ -132,6 +151,14 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
         viewport.ForceRefreshFinalImage();
         viewport.ForceRefreshFinalImage();
     }
     }
 
 
+    private static void OnRenderInDocSizeChanged(AvaloniaPropertyChangedEventArgs<bool> args)
+    {
+        FixedViewport? viewport = (FixedViewport)args.Sender;
+        viewport.CustomRenderSize = args.NewValue.Value ? viewport.Document?.SizeBindable ?? VecI.Zero : VecI.Zero;
+        viewport.InvalidateMeasure();
+        viewport.ForceRefreshFinalImage();
+    }
+
     private void DocSizeChanged(object? sender, DocumentSizeChangedEventArgs e)
     private void DocSizeChanged(object? sender, DocumentSizeChangedEventArgs e)
     {
     {
         Document?.Operations.AddOrUpdateViewport(GetLocation());
         Document?.Operations.AddOrUpdateViewport(GetLocation());

+ 62 - 18
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -19,6 +19,7 @@
     xmlns:input="clr-namespace:PixiEditor.Views.Input"
     xmlns:input="clr-namespace:PixiEditor.Views.Input"
     xmlns:controls="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
     xmlns:controls="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
     xmlns:ui1="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
     xmlns:ui1="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+    xmlns:handlers="clr-namespace:PixiEditor.Models.Handlers"
     mc:Ignorable="d"
     mc:Ignorable="d"
     x:Name="vpUc"
     x:Name="vpUc"
     d:DesignHeight="450"
     d:DesignHeight="450"
@@ -140,11 +141,11 @@
                         <TextBlock HorizontalAlignment="Center"
                         <TextBlock HorizontalAlignment="Center"
                                    ui:Translator.Key="GRIDLINES_SIZE" Margin="0 0 0 10" />
                                    ui:Translator.Key="GRIDLINES_SIZE" Margin="0 0 0 10" />
                         <controls:NumberInput Min="1"
                         <controls:NumberInput Min="1"
-                                           Max="1024"
-                                           Value="{Binding GridLinesXSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />
+                                              Max="1024"
+                                              Value="{Binding GridLinesXSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />
                         <controls:NumberInput Min="1"
                         <controls:NumberInput Min="1"
-                                           Max="1024"
-                                           Value="{Binding GridLinesYSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />
+                                              Max="1024"
+                                              Value="{Binding GridLinesYSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />
                         <Separator />
                         <Separator />
                         <TextBlock ui:Translator.Key="RENDER_PREVIEW" />
                         <TextBlock ui:Translator.Key="RENDER_PREVIEW" />
                         <ComboBox
                         <ComboBox
@@ -168,26 +169,69 @@
             ZIndex="100" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10">
             ZIndex="100" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10">
             <Grid.RowDefinitions>
             <Grid.RowDefinitions>
                 <RowDefinition MinHeight="40" MaxHeight="120" />
                 <RowDefinition MinHeight="40" MaxHeight="120" />
-                <RowDefinition Height="35" />
+                <RowDefinition Height="30" />
                 <RowDefinition Height="*" />
                 <RowDefinition Height="*" />
             </Grid.RowDefinitions>
             </Grid.RowDefinitions>
             <tools:Toolbar
             <tools:Toolbar
                 DataContext="{Binding Source={viewModels:MainVM}, Path=.}" />
                 DataContext="{Binding Source={viewModels:MainVM}, Path=.}" />
-            <Border Grid.Row="1" ClipToBounds="False"
-                    HorizontalAlignment="Left"
-                    Padding="5 0"
-                    Margin="0 2.5 0 5"
-                    Height="25"
-                    BorderBrush="{DynamicResource ThemeBorderMidBrush}"
-                    BorderThickness="1"
-                    Background="{DynamicResource ThemeBackgroundBrush2}"
-                    CornerRadius="{DynamicResource ControlCornerRadius}">
-                <TextBlock
-                    ui:Translator.Key="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ActiveToolSet.Name}"
-                    VerticalAlignment="Center" />
-            </Border>
+            <StackPanel Margin="0, 5, 0, 0" Background="Transparent" Grid.Row="1" Orientation="Horizontal" Name="toolsetsPanel">
+                <Border ClipToBounds="False"
+                        HorizontalAlignment="Left"
+                        Padding="5 0"
+                        Name="activeToolSetChip"
+                        Height="25"
+                        BorderBrush="{DynamicResource ThemeBorderHighBrush}"
+                        BorderThickness="1"
+                        Background="{DynamicResource ThemeBackgroundBrush2}"
+                        CornerRadius="{DynamicResource ControlCornerRadius}">
+                    <TextBlock>
+                        <Run Classes="pixi-icon"
+                             Text="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ActiveToolSet.Icon}" />
+                        <Run
+                            ui:Translator.Key="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ActiveToolSet.Name}" />
+                    </TextBlock>
+                </Border>
+                <ItemsControl
+                    Name="otherToolSets"
+                    Background="Transparent"
+                    ItemsSource="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.NonSelectedToolSets, Mode=OneWay}">
+                    <ItemsControl.ItemsPanel>
+                        <ItemsPanelTemplate>
+                            <StackPanel Margin="5, 0, 0, 0" Spacing="5"
+                                        Orientation="Horizontal" />
+                        </ItemsPanelTemplate>
+                    </ItemsControl.ItemsPanel>
+                    <ItemsControl.ItemTemplate>
+                        <DataTemplate x:DataType="{x:Type handlers:IToolSetHandler}">
+                            <Button
+                                Command="{xaml:Command PixiEditor.Tools.SetActiveToolSet, UseProvided=True}"
+                                CommandParameter="{Binding}"
+                                FontSize="{DynamicResource FontSizeMedium}"
+                                VerticalAlignment="Center">
+                                <TextBlock>
+                                    <Run Classes="pixi-icon" Text="{Binding Icon}" />
+                                    <Run ui:Translator.Key="{Binding Name}" />
+                                </TextBlock>
+                            </Button>
+                        </DataTemplate>
+                    </ItemsControl.ItemTemplate>
+                    <ItemsControl.IsVisible>
+                        <MultiBinding Converter="{converters:AnyTrueConverter}">
+                            <MultiBinding.Bindings>
+                                <Binding Path="IsPointerOver" ElementName="toolsPicker" />
+                                <Binding Path="IsPointerOver" ElementName="activeToolSetChip" />
+                                <Binding Path="IsPointerOver" ElementName="otherToolSets" />
+                            </MultiBinding.Bindings>
+                        </MultiBinding>
+                    </ItemsControl.IsVisible>
+                </ItemsControl>
+            </StackPanel>
             <tools:ToolsPicker Grid.Row="2" IsHitTestVisible="True"
             <tools:ToolsPicker Grid.Row="2" IsHitTestVisible="True"
+                               Name="toolsPicker"
+                               Padding="0, 5, 0, 0"
+                               Background="Transparent"
                                HorizontalAlignment="Left"
                                HorizontalAlignment="Left"
+                               Margin="0, 0, 0, -95"
                                ToolSet="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ActiveToolSet, Mode=TwoWay}"
                                ToolSet="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ActiveToolSet, Mode=TwoWay}"
                                ToolSets="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.AllToolSets, Mode=OneWay}"
                                ToolSets="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.AllToolSets, Mode=OneWay}"
                                SwitchToolSetCommand="{xaml:Command Name=PixiEditor.Tools.SwitchToolSet, UseProvided=True}" />
                                SwitchToolSetCommand="{xaml:Command Name=PixiEditor.Tools.SwitchToolSet, UseProvided=True}" />

+ 47 - 11
src/PixiEditor/Views/Visuals/PreviewPainterControl.cs

@@ -17,6 +17,15 @@ public class PreviewPainterControl : DrawieControl
         AvaloniaProperty.Register<PreviewPainterControl, PreviewPainter>(
         AvaloniaProperty.Register<PreviewPainterControl, PreviewPainter>(
             nameof(PreviewPainter));
             nameof(PreviewPainter));
 
 
+    public static readonly StyledProperty<VecI> CustomRenderSizeProperty = AvaloniaProperty.Register<PreviewPainterControl, VecI>(
+        nameof(CustomRenderSize));
+
+    public VecI CustomRenderSize
+    {
+        get => GetValue(CustomRenderSizeProperty);
+        set => SetValue(CustomRenderSizeProperty, value);
+    }
+
     public PreviewPainter PreviewPainter
     public PreviewPainter PreviewPainter
     {
     {
         get => GetValue(PreviewPainterProperty);
         get => GetValue(PreviewPainterProperty);
@@ -35,6 +44,7 @@ public class PreviewPainterControl : DrawieControl
     {
     {
         PreviewPainterProperty.Changed.Subscribe(PainterChanged);
         PreviewPainterProperty.Changed.Subscribe(PainterChanged);
         BoundsProperty.Changed.Subscribe(UpdatePainterBounds);
         BoundsProperty.Changed.Subscribe(UpdatePainterBounds);
+        CustomRenderSizeProperty.Changed.Subscribe(UpdatePainterBounds);
     }
     }
 
 
     public PreviewPainterControl()
     public PreviewPainterControl()
@@ -64,10 +74,10 @@ public class PreviewPainterControl : DrawieControl
         if (args.NewValue.Value != null)
         if (args.NewValue.Value != null)
         {
         {
             sender.painterInstance = args.NewValue.Value.AttachPainterInstance();
             sender.painterInstance = args.NewValue.Value.AttachPainterInstance();
-            if (sender.Bounds is { Width: > 0, Height: > 0 })
+            VecI finalSize = sender.GetFinalSize();
+            if (finalSize is { X: > 0, Y: > 0 })
             {
             {
-                sender.PreviewPainter.ChangeRenderTextureSize(sender.painterInstance.RequestId,
-                    new VecI((int)sender.Bounds.Width, (int)sender.Bounds.Height));
+                sender.PreviewPainter.ChangeRenderTextureSize(sender.painterInstance.RequestId, finalSize);
             }
             }
 
 
             sender.painterInstance.RequestMatrix = sender.OnPainterRequestMatrix;
             sender.painterInstance.RequestMatrix = sender.OnPainterRequestMatrix;
@@ -93,23 +103,49 @@ public class PreviewPainterControl : DrawieControl
             return;
             return;
         }
         }
 
 
+        if (CustomRenderSize.ShortestAxis > 0)
+        {
+            surface.Canvas.Save();
+            VecI finalSize = GetFinalSize();
+            surface.Canvas.Scale(
+                (float)Bounds.Width / finalSize.X,
+                (float)Bounds.Height / finalSize.Y);
+        }
+
         PreviewPainter.Paint(surface, painterInstance.RequestId);
         PreviewPainter.Paint(surface, painterInstance.RequestId);
+
+        if (CustomRenderSize.ShortestAxis > 0)
+        {
+            surface.Canvas.Restore();
+        }
     }
     }
 
 
     private Matrix3X3 UniformScale(float x, float y, RectD previewBounds)
     private Matrix3X3 UniformScale(float x, float y, RectD previewBounds)
     {
     {
-        float scaleX = (float)Bounds.Width / x;
-        float scaleY = (float)Bounds.Height / y;
+        VecI finalSize = GetFinalSize();
+        float scaleX = finalSize.X / x;
+        float scaleY = finalSize.Y / y;
         var scale = Math.Min(scaleX, scaleY);
         var scale = Math.Min(scaleX, scaleY);
-        float dX = (float)Bounds.Width / 2 / scale - x / 2;
+        float dX = (float)finalSize.X / 2 / scale - x / 2;
         dX -= (float)previewBounds.X;
         dX -= (float)previewBounds.X;
-        float dY = (float)Bounds.Height / 2 / scale - y / 2;
+        float dY = (float)finalSize.Y / 2 / scale - y / 2;
         dY -= (float)previewBounds.Y;
         dY -= (float)previewBounds.Y;
         Matrix3X3 matrix = Matrix3X3.CreateScale(scale, scale);
         Matrix3X3 matrix = Matrix3X3.CreateScale(scale, scale);
         return matrix.Concat(Matrix3X3.CreateTranslation(dX, dY));
         return matrix.Concat(Matrix3X3.CreateTranslation(dX, dY));
     }
     }
 
 
-    private static void UpdatePainterBounds(AvaloniaPropertyChangedEventArgs<Rect> args)
+    private VecI GetFinalSize()
+    {
+        VecI finalSize = CustomRenderSize.ShortestAxis > 0 ? CustomRenderSize : new VecI((int)Bounds.Width, (int)Bounds.Height);
+        if(Bounds.Width < finalSize.X && Bounds.Height < finalSize.Y)
+        {
+            finalSize = new VecI((int)Bounds.Width, (int)Bounds.Height);
+        }
+
+        return finalSize;
+    }
+
+    private static void UpdatePainterBounds(AvaloniaPropertyChangedEventArgs args)
     {
     {
         var sender = args.Sender as PreviewPainterControl;
         var sender = args.Sender as PreviewPainterControl;
 
 
@@ -120,10 +156,10 @@ public class PreviewPainterControl : DrawieControl
 
 
         if (sender.painterInstance != null)
         if (sender.painterInstance != null)
         {
         {
-            if (args.NewValue.Value is { Width: > 0, Height: > 0 })
+            VecI finalSize = sender.GetFinalSize();
+            if (finalSize is { X: > 0, Y: > 0 })
             {
             {
-                sender.PreviewPainter.ChangeRenderTextureSize(sender.painterInstance.RequestId,
-                    new VecI((int)args.NewValue.Value.Width, (int)args.NewValue.Value.Height));
+                sender.PreviewPainter.ChangeRenderTextureSize(sender.painterInstance.RequestId, finalSize);
                 sender.PreviewPainter.RepaintFor(sender.painterInstance.RequestId);
                 sender.PreviewPainter.RepaintFor(sender.painterInstance.RequestId);
             }
             }
         }
         }