Browse Source

Merge pull request #653 from PixiEditor/fixes-27-11

Fixes 27 11
Krzysztof Krysiński 8 months ago
parent
commit
a6c02bedbb
27 changed files with 294 additions and 120 deletions
  1. 1 1
      src/Drawie
  2. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyShapeVectorData.cs
  3. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  4. 18 9
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs
  5. 7 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs
  6. 20 10
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs
  7. 5 1
      src/PixiEditor/Data/Localization/Languages/en.json
  8. 14 0
      src/PixiEditor/Helpers/Extensions/DataObjectExtensions.cs
  9. 14 12
      src/PixiEditor/Models/Controllers/ClipboardController.cs
  10. 50 15
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs
  11. 1 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs
  12. 9 11
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterEllipseToolExecutor.cs
  13. 19 14
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterRectangleToolExecutor.cs
  14. 12 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs
  15. 32 16
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorEllipseToolExecutor.cs
  16. 2 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorLineToolExecutor.cs
  17. 5 2
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs
  18. 36 15
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorRectangleToolExecutor.cs
  19. 1 1
      src/PixiEditor/Models/Handlers/IToolsHandler.cs
  20. 3 0
      src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs
  21. 6 3
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  22. 2 0
      src/PixiEditor/ViewModels/Tools/Tools/VectorEllipseToolViewModel.cs
  23. 2 0
      src/PixiEditor/ViewModels/Tools/Tools/VectorLineToolViewModel.cs
  24. 27 2
      src/PixiEditor/ViewModels/Tools/Tools/VectorPathToolViewModel.cs
  25. 4 3
      src/PixiEditor/ViewModels/Tools/Tools/VectorRectangleToolViewModel.cs
  26. 1 1
      src/PixiEditor/ViewModels/ViewModelMain.cs
  27. 1 0
      src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 47a57ed9ed23a0343cd7a7328cbfb8d6a111e14a
+Subproject commit 252c43e10a70ac0e560630ca5623355a5e50de7a

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

@@ -12,4 +12,5 @@ public interface IReadOnlyShapeVectorData
     public int StrokeWidth { get; }
     public RectD GeometryAABB { get; }
     public RectD TransformedAABB { get; }
+    public ShapeCorners TransformationCorners { get; }
 }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs

@@ -19,6 +19,7 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource, IPrev
     public FolderNode()
     {
         Content = CreateRenderInput(ContentInternalName, "CONTENT");
+        AllowHighDpiRendering = true;
     }
 
     public override Node CreateCopy() => new FolderNode { MemberName = MemberName };

+ 18 - 9
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs

@@ -46,15 +46,24 @@ public class EllipseVectorData : ShapeVectorData, IReadOnlyEllipseData
         }
 
         using Paint shapePaint = new Paint() { IsAntiAliased = true };
-        
-        shapePaint.Color = FillColor;
-        shapePaint.Style = PaintStyle.Fill;
-        drawingSurface.Canvas.DrawOval(Center, Radius, shapePaint);
-
-        shapePaint.Color = StrokeColor;
-        shapePaint.Style = PaintStyle.Stroke;
-        shapePaint.StrokeWidth = StrokeWidth;
-        drawingSurface.Canvas.DrawOval(Center, Radius - new VecD(StrokeWidth / 2f), shapePaint);
+
+        if (Radius.ShortestAxis < StrokeWidth)
+        {
+            shapePaint.Color = StrokeColor;
+            shapePaint.Style = PaintStyle.Fill;
+            drawingSurface.Canvas.DrawOval(Center, Radius, shapePaint);
+        }
+        else
+        {
+            shapePaint.Color = FillColor;
+            shapePaint.Style = PaintStyle.Fill;
+            drawingSurface.Canvas.DrawOval(Center, Radius, shapePaint);
+
+            shapePaint.Color = StrokeColor;
+            shapePaint.Style = PaintStyle.Stroke;
+            shapePaint.StrokeWidth = StrokeWidth;
+            drawingSurface.Canvas.DrawOval(Center, Radius - new VecD(StrokeWidth / 2f), shapePaint);
+        }
 
         if (applyTransform)
         {

+ 7 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs

@@ -82,6 +82,12 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
 
     public override object Clone()
     {
-        return new PathVectorData(new VectorPath(Path));
+        return new PathVectorData(new VectorPath(Path))
+        {
+            StrokeColor = StrokeColor,
+            FillColor = FillColor,
+            StrokeWidth = StrokeWidth,
+            TransformationMatrix = TransformationMatrix
+        };
     }
 }

+ 20 - 10
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs

@@ -12,7 +12,7 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
     public VecD Center { get; }
     public VecD Size { get; }
 
-    public override RectD GeometryAABB => RectD.FromCenterAndSize(Center, Size); 
+    public override RectD GeometryAABB => RectD.FromCenterAndSize(Center, Size);
 
     public override ShapeCorners TransformationCorners =>
         new ShapeCorners(Center, Size).WithMatrix(TransformationMatrix);
@@ -44,15 +44,25 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
         }
 
         using Paint paint = new Paint() { IsAntiAliased = true };
-        
-        paint.Color = FillColor;
-        paint.Style = PaintStyle.Fill;
-        drawingSurface.Canvas.DrawRect(RectD.FromCenterAndSize(Center, Size), paint);
-
-        paint.Color = StrokeColor;
-        paint.Style = PaintStyle.Stroke;
-        paint.StrokeWidth = StrokeWidth;
-        drawingSurface.Canvas.DrawRect(RectD.FromCenterAndSize(Center, Size - new VecD(StrokeWidth)), paint);
+
+        if (Size.ShortestAxis < StrokeWidth)
+        {
+            paint.Color = StrokeColor;
+            paint.Style = PaintStyle.Fill;
+            drawingSurface.Canvas.DrawRect(RectD.FromCenterAndSize(Center, Size), paint);
+        }
+        else
+        {
+            paint.Color = FillColor;
+            paint.Style = PaintStyle.Fill;
+            drawingSurface.Canvas.DrawRect(RectD.FromCenterAndSize(Center, Size), paint);
+
+            paint.Color = StrokeColor;
+            paint.Style = PaintStyle.Stroke;
+
+            paint.StrokeWidth = StrokeWidth;
+            drawingSurface.Canvas.DrawRect(RectD.FromCenterAndSize(Center, Size - new VecD(StrokeWidth)), paint);
+        }
 
         if (applyTransform)
         {

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

@@ -765,5 +765,9 @@
   "LOW_RES_PREVIEW": "Document Resolution Preview",
   "TOGGLE_HIGH_RES_PREVIEW": "Toggle high resolution preview",
   "FACTOR": "Factor",
-  "PATH_TOOL": "Path"
+  "PATH_TOOL": "Path",
+  "PATH_TOOL_TOOLTIP": "Create vector paths and curves ({0}).",
+  "PATH_TOOL_ACTION_DISPLAY": "Click to add a point.",
+  "PATH_TOOL_ACTION_DISPLAY_CTRL": "Click on existing point and drag to make it a curve.", 
+  "PATH_TOOL_ACTION_DISPLAY_ALT": "Click on a control point and move to adjust only one side of the curve."
 }

+ 14 - 0
src/PixiEditor/Helpers/Extensions/DataObjectExtensions.cs

@@ -61,6 +61,20 @@ public static class DataObjectExtensions
 
         return VecI.FromBytes(bytes);
     }
+    
+    public static VecD GetVecD(this IDataObject data, string format)
+    {
+        if (!data.Contains(format))
+            return new VecD(-1, -1);
+
+        byte[] bytes = (byte[])data.Get(format);
+
+        if (bytes is { Length: < 16 })
+            return new VecD(-1, -1);
+
+        return VecD.FromBytes(bytes);
+    }
 
     public static void SetVecI(this DataObject data, string format, VecI value) => data.Set(format, value.ToByteArray());
+    public static void SetVecD(this DataObject data, string format, VecD value) => data.Set(format, value.ToByteArray());
 }

+ 14 - 12
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -3,6 +3,8 @@ using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.Unicode;
 using System.Threading.Tasks;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
@@ -107,16 +109,16 @@ internal static class ClipboardController
 
         if (copyArea.Size != document.SizeBindable && copyArea.Pos != VecI.Zero && copyArea != RectI.Empty)
         {
-            data.SetVecI(ClipboardDataFormats.PositionFormat, copyArea.Pos);
+            data.SetVecD(ClipboardDataFormats.PositionFormat, copyArea.Pos);
         }
 
         string[] layerIds = document.GetSelectedMembers().Select(x => x.ToString()).ToArray();
         string layerIdsString = string.Join(";", layerIds);
 
-        byte[] layerIdsBytes = System.Text.Encoding.UTF8.GetBytes(layerIdsString);
+        byte[] layerIdsBytes = Encoding.UTF8.GetBytes(layerIdsString);
 
         data.Set(ClipboardDataFormats.LayerIdList, layerIdsBytes);
-        data.Set(ClipboardDataFormats.DocumentFormat, document.Id);
+        data.Set(ClipboardDataFormats.DocumentFormat, Encoding.UTF8.GetBytes(document.Id.ToString()));
 
         await Clipboard.SetDataObjectAsync(data);
     }
@@ -209,11 +211,11 @@ internal static class ClipboardController
         var dataObjects = data as IDataObject[] ?? data.ToArray();
         
         var dataObjectWithPos = dataObjects.FirstOrDefault(x => x.Contains(ClipboardDataFormats.PositionFormat));
-        VecI pos = VecI.Zero;
+        VecD pos = VecD.Zero;
 
         if (dataObjectWithPos != null)
         {
-            pos = dataObjectWithPos.GetVecI(ClipboardDataFormats.PositionFormat);
+            pos = dataObjectWithPos.GetVecD(ClipboardDataFormats.PositionFormat);
         }
         
         for (var i = 0; i < layerIds.Length; i++)
@@ -221,7 +223,7 @@ internal static class ClipboardController
             var layerId = layerIds[i];
 
             var layer = doc.StructureHelper.Find(layerId);
-            if (layer is not { TightBounds: not null } || layer.TightBounds.Value.Pos != pos)
+            if (layer is not { TightBounds: not null } || layer.TightBounds.Value.Pos.AlmostEquals(pos))
                 return false;
         }
         
@@ -311,7 +313,7 @@ internal static class ClipboardController
         if (data == null)
             return surfaces;
 
-        VecI pos = VecI.Zero;
+        VecD pos = VecD.Zero;
 
         foreach (var dataObject in data)
         {
@@ -319,18 +321,18 @@ internal static class ClipboardController
             {
                 surfaces.Add(new DataImage(singleImage,
                     dataObject.Contains(ClipboardDataFormats.PositionFormat)
-                        ? dataObject.GetVecI(ClipboardDataFormats.PositionFormat)
-                        : pos));
+                        ? (VecI)dataObject.GetVecD(ClipboardDataFormats.PositionFormat)
+                        : (VecI)pos));
                 continue;
             }
 
             if (dataObject.Contains(ClipboardDataFormats.PositionFormat))
             {
-                pos = dataObject.GetVecI(ClipboardDataFormats.PositionFormat);
+                pos = dataObject.GetVecD(ClipboardDataFormats.PositionFormat);
                 for (var i = 0; i < surfaces.Count; i++)
                 {
                     var surface = surfaces[i];
-                    surfaces[i] = surface with { Position = pos };
+                    surfaces[i] = surface with { Position = (VecI)pos };
                 }
             }
 
@@ -366,7 +368,7 @@ internal static class ClipboardController
 
                     string filename = Path.GetFullPath(path);
                     surfaces.Add(new DataImage(filename, imported,
-                        dataObject.GetVecI(ClipboardDataFormats.PositionFormat)));
+                        (VecI)dataObject.GetVecD(ClipboardDataFormats.PositionFormat)));
                 }
                 catch
                 {

+ 50 - 15
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs

@@ -26,9 +26,10 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     protected bool drawOnMask;
 
     protected T? toolViewModel;
-    protected RectI lastRect;
+    protected RectD lastRect;
     protected double lastRadians;
-
+    
+    private ShapeCorners initialCorners;
     private bool noMovement = true;
     protected IBasicShapeToolbar toolbar;
     private IColorsHandler? colorsVM;
@@ -60,6 +61,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
                 toolbar.FillColor = colorsVM.PrimaryColor.ToColor();
                 toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
             }
+            
+            lastRect = new RectD(startDrawingPos, VecD.Zero);
 
             document!.TransformHandler.ShowTransform(TransformMode, false, new ShapeCorners((RectD)lastRect.Inflate(1)),
                 false);
@@ -81,6 +84,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             toolbar.FillColor = shapeData.FillColor.ToColor();
             toolbar.ToolSize = shapeData.StrokeWidth;
             toolbar.Fill = shapeData.FillColor != Colors.Transparent;
+            initialCorners = shapeData.TransformationCorners;
+            
             ActiveMode = ShapeToolMode.Transform;
         }
         else
@@ -91,7 +96,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         return ExecutionState.Success;
     }
 
-    protected abstract void DrawShape(VecI currentPos, double rotationRad, bool firstDraw);
+    protected abstract void DrawShape(VecD currentPos, double rotationRad, bool firstDraw);
     protected abstract IAction SettingsChangedAction();
     protected abstract IAction TransformMovedAction(ShapeData data, ShapeCorners corners);
     protected virtual bool InitShapeData(IReadOnlyShapeVectorData data) { return true; }
@@ -110,6 +115,17 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         return (VecI)pos1;
     }
 
+    public static VecD GetSquaredPosition(VecD startPos, VecD curPos)
+    {
+        VecD pos1 = curPos.ProjectOntoLine(startPos, startPos + new VecD(1, 1)) -
+                    new VecD(0.25).Multiply((curPos - (VecI)startPos).Signs());
+        VecD pos2 = curPos.ProjectOntoLine(startPos, startPos + new VecD(1, -1)) -
+                    new VecD(0.25).Multiply((curPos - (VecI)startPos).Signs());
+        if ((pos1 - curPos).LengthSquared > (pos2 - curPos).LengthSquared)
+            return pos2;
+        return pos1;
+    }
+
     public static RectI GetSquaredCoordinates(VecI startPos, VecI curPos)
     {
         VecI pos = GetSquaredPosition(startPos, curPos);
@@ -175,12 +191,12 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
     protected override void PrecisePositionChangeDrawingMode(VecD pos)
     {
-        VecI adjustedPos = (VecI)pos.Floor();
+        VecD adjustedPos = AlignToPixels ? (VecI)pos.Floor() : pos;
 
         VecD snapped = adjustedPos;
         if (toolViewModel.DrawEven)
         {
-            adjustedPos = GetSquaredPosition((VecI)startDrawingPos, adjustedPos);
+            adjustedPos = AlignToPixels ? GetSquaredPosition((VecI)startDrawingPos, (VecI)adjustedPos) : GetSquaredPosition(startDrawingPos, adjustedPos);
             VecD dir = (adjustedPos - startDrawingPos).Normalize();
             snapped = Snap(adjustedPos, startDrawingPos, dir, true);
         }
@@ -191,7 +207,14 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
         noMovement = false;
 
-        DrawShape((VecI)snapped.Floor(), lastRadians, false);
+        if (AlignToPixels)
+        {
+            DrawShape((VecI)snapped.Floor(), lastRadians, false);
+        }
+        else
+        {
+            DrawShape(snapped, lastRadians, false);
+        }
 
         document!.TransformHandler.ShowTransform(TransformMode, false, new ShapeCorners((RectD)lastRect), false);
         document!.TransformHandler.Corners = new ShapeCorners((RectD)lastRect);
@@ -208,16 +231,19 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             HighlightSnapAxis(snapXAxis, snapYAxis);
         }
 
-        if (snapped != VecI.Zero)
+        if (AlignToPixels)
         {
-            if (adjustPos.X < pos.X)
+            if (snapped != VecI.Zero)
             {
-                snapped -= new VecI(1, 0);
-            }
-
-            if (adjustPos.Y < pos.Y)
-            {
-                snapped -= new VecI(0, 1);
+                if (adjustPos.X < pos.X)
+                {
+                    snapped -= new VecI(1, 0);
+                }
+
+                if (adjustPos.Y < pos.Y)
+                {
+                    snapped -= new VecI(0, 1);
+                }
             }
         }
 
@@ -281,13 +307,22 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         base.OnLeftMouseButtonUp(argsPositionOnCanvas);
     }
 
+    protected override void StopMode(ShapeToolMode mode)
+    {
+        base.StopMode(mode);
+        if (mode == ShapeToolMode.Drawing)
+        {
+            initialCorners = new ShapeCorners((RectD)lastRect);
+        }
+    }
+
     protected override void StartMode(ShapeToolMode mode)
     {
         base.StartMode(mode);
         if (mode == ShapeToolMode.Transform)
         {
             document.TransformHandler.HideTransform();
-            document!.TransformHandler.ShowTransform(TransformMode, false, new ShapeCorners((RectD)lastRect), true);
+            document!.TransformHandler.ShowTransform(TransformMode, false, initialCorners, true);
         }
     }
 

+ 1 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs

@@ -19,6 +19,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
 
     protected Color StrokeColor => toolbar!.StrokeColor.ToColor();
     protected int StrokeWidth => toolViewModel!.ToolSize;
+    
     protected bool drawOnMask;
 
     protected VecD curPos;

+ 9 - 11
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterEllipseToolExecutor.cs

@@ -11,18 +11,16 @@ namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 #nullable enable
 internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterEllipseToolHandler>
 {
-    private void DrawEllipseOrCircle(VecI curPos, double rotationRad, bool firstDraw)
+    private void DrawEllipseOrCircle(VecD curPos, double rotationRad, bool firstDraw)
     {
         RectI rect;
         VecI startPos = (VecI)Snap(startDrawingPos, curPos).Floor();
         if (firstDraw)
-            rect = new RectI(curPos, VecI.Zero);
-        /*else if (toolViewModel!.DrawCircle)
-            rect = GetSquaredCoordinates(startPos, curPos);
-        else*/
-            rect = RectI.FromTwoPixels(startPos, curPos);
+            rect = new RectI((VecI)curPos, VecI.Zero);
+        else
+            rect = RectI.FromTwoPixels(startPos, (VecI)curPos);
 
-        lastRect = rect;
+        lastRect = (RectD)rect;
         lastRadians = rotationRad;
 
         internals!.ActionAccumulator.AddActions(new DrawRasterEllipse_Action(memberId, rect, rotationRad, StrokeColor, FillColor, StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable));
@@ -30,10 +28,10 @@ internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterElli
 
     public override ExecutorType Type => ExecutorType.ToolLinked;
     protected override DocumentTransformMode TransformMode => DocumentTransformMode.Scale_Rotate_NoShear_NoPerspective;
-    protected override void DrawShape(VecI currentPos, double rotationRad, bool firstDraw) => DrawEllipseOrCircle(currentPos, rotationRad, firstDraw);
+    protected override void DrawShape(VecD currentPos, double rotationRad, bool firstDraw) => DrawEllipseOrCircle(currentPos, rotationRad, firstDraw);
     protected override IAction SettingsChangedAction()
     {
-        return new DrawRasterEllipse_Action(memberId, lastRect, lastRadians, StrokeColor, FillColor, StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
+        return new DrawRasterEllipse_Action(memberId, (RectI)lastRect, lastRadians, StrokeColor, FillColor, StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
     }
 
     protected override IAction TransformMovedAction(ShapeData data, ShapeCorners corners)
@@ -41,10 +39,10 @@ internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterElli
         RectI rect = (RectI)RectD.FromCenterAndSize(data.Center, data.Size);
         double radians = corners.RectRotation;
         
-        lastRect = rect;
+        lastRect = (RectD)rect;
         lastRadians = radians;
         
-        return new DrawRasterEllipse_Action(memberId, lastRect, lastRadians, StrokeColor,
+        return new DrawRasterEllipse_Action(memberId, (RectI)lastRect, lastRadians, StrokeColor,
             FillColor, StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
     }
 

+ 19 - 14
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterRectangleToolExecutor.cs

@@ -12,44 +12,49 @@ internal class RasterRectangleToolExecutor : DrawableShapeToolExecutor<IRasterRe
 {
     private ShapeData lastData;
     public override ExecutorType Type => ExecutorType.ToolLinked;
-    private void DrawRectangle(VecI curPos, double rotationRad, bool firstDraw)
+
+    private void DrawRectangle(VecD curPos, double rotationRad, bool firstDraw)
     {
         RectI rect;
         VecI startPos = (VecI)Snap(startDrawingPos, curPos).Floor();
         if (firstDraw)
-            rect = new RectI(curPos, VecI.Zero);
-        /*else if (toolViewModel!.DrawSquare)
-            rect = GetSquaredCoordinates(startPos, curPos);
-        else*/
-            rect = RectI.FromTwoPixels(startPos, curPos);
-        lastRect = rect;
-        lastRadians = rotationRad;
+            rect = new RectI((VecI)curPos, VecI.Zero);
+        else
+            rect = RectI.FromTwoPixels(startPos, (VecI)curPos);
         
+        lastRect = (RectD)rect;
+        lastRadians = rotationRad;
+
         lastData = new ShapeData(rect.Center, rect.Size, rotationRad, StrokeWidth, StrokeColor, FillColor)
         {
             AntiAliasing = toolbar.AntiAliasing
         };
 
-        internals!.ActionAccumulator.AddActions(new DrawRasterRectangle_Action(memberId, lastData, drawOnMask, document!.AnimationHandler.ActiveFrameBindable));
+        internals!.ActionAccumulator.AddActions(new DrawRasterRectangle_Action(memberId, lastData, drawOnMask,
+            document!.AnimationHandler.ActiveFrameBindable));
     }
 
-    protected override void DrawShape(VecI currentPos, double rotationRad, bool first) => DrawRectangle(currentPos, rotationRad, first);
+    protected override void DrawShape(VecD currentPos, double rotationRad, bool first) =>
+        DrawRectangle(currentPos, rotationRad, first);
+
     protected override IAction SettingsChangedAction()
     {
         lastData = new ShapeData(lastData.Center, lastData.Size, lastRadians, StrokeWidth, StrokeColor, FillColor)
         {
             AntiAliasing = toolbar.AntiAliasing
         };
-        return new DrawRasterRectangle_Action(memberId, lastData, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);   
+        return new DrawRasterRectangle_Action(memberId, lastData, drawOnMask,
+            document!.AnimationHandler.ActiveFrameBindable);
     }
 
     protected override IAction TransformMovedAction(ShapeData data, ShapeCorners corners)
     {
         lastData = data;
-        
+
         lastRadians = corners.RectRotation;
-        
-        return new DrawRasterRectangle_Action(memberId, data, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
+
+        return new DrawRasterRectangle_Action(memberId, data, drawOnMask,
+            document!.AnimationHandler.ActiveFrameBindable);
     }
 
     protected override IAction EndDrawAction() => new EndDrawRasterRectangle_Action();

+ 12 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs

@@ -40,7 +40,8 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
             StartMode(activeMode);
         }
     }
-
+    protected virtual bool AlignToPixels { get; } = true;
+    
     protected Guid memberId;
     protected VecD startDrawingPos;
 
@@ -85,7 +86,15 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
 
     private void StartDrawingMode()
     {
-        startDrawingPos = SnapAndHighlight(controller.LastPrecisePosition);
+        var snapped = SnapAndHighlight(controller.LastPrecisePosition);
+        if (AlignToPixels)
+        {
+            startDrawingPos = (VecI)snapped;
+        }
+        else
+        {
+            startDrawingPos = snapped;
+        }
     }
 
     protected virtual void StopMode(ShapeToolMode mode)
@@ -180,7 +189,7 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
     {
         VecD snapped = document.SnappingHandler.SnappingController.GetSnapPoint(pos, out string snapX, out string snapY);
         HighlightSnapping(snapX, snapY);
-        return (VecI)snapped;
+        return snapped;
     }
 
     protected virtual void PrecisePositionChangePreviewMode(VecD pos)

+ 32 - 16
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorEllipseToolExecutor.cs

@@ -18,31 +18,31 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
 
     private VecD firstRadius;
     private VecD firstCenter;
-    
+
     private Matrix3X3 lastMatrix = Matrix3X3.Identity;
 
+    protected override bool AlignToPixels => false;
+
     protected override bool InitShapeData(IReadOnlyShapeVectorData data)
     {
         if (data is not EllipseVectorData ellipseData)
             return false;
-        
+
         firstCenter = ellipseData.Center;
         firstRadius = ellipseData.Radius;
         lastMatrix = ellipseData.TransformationMatrix;
-        
+
         return true;
     }
 
-    protected override void DrawShape(VecI curPos, double rotationRad, bool firstDraw)
+    protected override void DrawShape(VecD curPos, double rotationRad, bool firstDraw)
     {
-        RectI rect;
-        VecI startPos = (VecI)Snap(startDrawingPos, curPos).Floor();
-        if (firstDraw)
-            rect = new RectI(curPos, VecI.Zero);
-        /*else if (toolViewModel!.DrawCircle)
-            rect = GetSquaredCoordinates(startPos, curPos);
-        else*/
-            rect = RectI.FromTwoPixels(startPos, curPos);
+        RectD rect;
+        VecD startPos = Snap(startDrawingPos, curPos);
+        if (!firstDraw)
+            rect = RectD.FromTwoPoints(startPos, curPos);
+        else
+            rect = new RectD(curPos, VecD.Zero);
 
         firstCenter = rect.Center;
         firstRadius = rect.Size / 2f;
@@ -62,17 +62,33 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
         return new SetShapeGeometry_Action(memberId,
             new EllipseVectorData(firstCenter, firstRadius)
             {
-                StrokeColor = StrokeColor, FillColor = FillColor, StrokeWidth = StrokeWidth,
+                StrokeColor = StrokeColor,
+                FillColor = FillColor,
+                StrokeWidth = StrokeWidth,
                 TransformationMatrix = lastMatrix
             });
     }
 
     protected override IAction TransformMovedAction(ShapeData data, ShapeCorners corners)
     {
-        RectI rect = (RectI)RectD.FromCenterAndSize(data.Center, data.Size);
+        RectD rect = RectD.FromCenterAndSize(data.Center, data.Size);
         RectD firstRect = RectD.FromCenterAndSize(firstCenter, firstRadius * 2);
-        Matrix3X3 matrix = OperationHelper.CreateMatrixFromPoints(corners, firstRadius * 2);
-        matrix = matrix.Concat(Matrix3X3.CreateTranslation(-(float)firstRect.TopLeft.X, -(float)firstRect.TopLeft.Y));
+
+        Matrix3X3 matrix = Matrix3X3.Identity;
+        if (corners.IsRect)
+        {
+            firstCenter = corners.RectCenter;
+            firstRadius = corners.RectSize / 2f;
+            
+            if(corners.RectRotation != 0)
+                matrix = Matrix3X3.CreateRotation((float)corners.RectRotation, (float)firstCenter.X, (float)firstCenter.Y);
+        }
+        else
+        {
+            matrix = OperationHelper.CreateMatrixFromPoints(corners, firstRadius * 2);
+            matrix = matrix.Concat(
+                Matrix3X3.CreateTranslation(-(float)firstRect.TopLeft.X, -(float)firstRect.TopLeft.Y));
+        }
 
         EllipseVectorData ellipseData = new EllipseVectorData(firstCenter, firstRadius)
         {

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

@@ -13,6 +13,8 @@ internal class VectorLineToolExecutor : LineExecutor<IVectorLineToolHandler>
     private VecD startPoint;
     private VecD endPoint;
 
+    protected override bool AlignToPixels => false;
+
     protected override bool InitShapeData(IReadOnlyLineData? data)
     {
         if (data is null)

+ 5 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -27,6 +27,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     private IVectorPathToolHandler vectorPathToolHandler;
     private IBasicShapeToolbar toolbar;
     private IColorsHandler colorHandler;
+    private bool isValidPathLayer;
 
     public override ExecutorType Type => ExecutorType.ToolLinked;
 
@@ -55,6 +56,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
         {
             var shapeData = vectorLayerHandler.GetShapeData(document.AnimationHandler.ActiveFrameTime);
             bool wasNull = false;
+            isValidPathLayer = true;
             if (shapeData is PathVectorData pathData)
             {
                 startingPath = new VectorPath(pathData.Path);
@@ -67,7 +69,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
             }
             else
             {
-                return ExecutionState.Error;
+                isValidPathLayer = false;
+                return ExecutionState.Success;
             }
 
             document.PathOverlayHandler.Show(startingPath, false);
@@ -130,7 +133,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
 
     public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
     {
-        if (startingPath.IsClosed)
+        if (!isValidPathLayer || startingPath.IsClosed) 
         {
             if (NeedsNewLayer(document.SelectedStructureMember, document.AnimationHandler.ActiveFrameTime))
             {

+ 36 - 15
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorRectangleToolExecutor.cs

@@ -21,28 +21,31 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
 
     private Matrix3X3 lastMatrix = Matrix3X3.Identity;
 
+    protected override bool AlignToPixels => false;
+
     protected override bool InitShapeData(IReadOnlyShapeVectorData data)
     {
         if (data is not RectangleVectorData rectData)
             return false;
-        
+
         firstCenter = rectData.Center;
         firstSize = rectData.Size;
         lastMatrix = rectData.TransformationMatrix;
-        
         return true;
     }
 
-    protected override void DrawShape(VecI curPos, double rotationRad, bool firstDraw)
+    protected override void DrawShape(VecD curPos, double rotationRad, bool firstDraw)
     {
-        RectI rect;
-        VecI startPos = (VecI)Snap(startDrawingPos, curPos).Floor();
+        RectD rect;
+        VecD startPos = Snap(startDrawingPos, curPos);
         if (firstDraw)
-            rect = new RectI(curPos, VecI.Zero);
-        /*else if (toolViewModel!.DrawSquare)
-            rect = GetSquaredCoordinates(startPos, curPos);
-        else*/
-            rect = RectI.FromTwoPixels(startPos, curPos);
+        {
+            rect = new RectD(curPos, VecD.Zero);
+        }
+        else
+        {
+            rect = RectD.FromTwoPoints(startPos, curPos);
+        }
 
         firstCenter = rect.Center;
         firstSize = rect.Size;
@@ -62,7 +65,9 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
         return new SetShapeGeometry_Action(memberId,
             new RectangleVectorData(firstCenter, firstSize)
             {
-                StrokeColor = StrokeColor, FillColor = FillColor, StrokeWidth = StrokeWidth,
+                StrokeColor = StrokeColor,
+                FillColor = FillColor,
+                StrokeWidth = StrokeWidth,
                 TransformationMatrix = lastMatrix
             });
     }
@@ -75,13 +80,29 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
             firstSize = data.Size;
         }
 
-        RectD firstRect = RectD.FromCenterAndSize(firstCenter, firstSize);
-        Matrix3X3 matrix = OperationHelper.CreateMatrixFromPoints(corners, firstSize);
-        matrix = matrix.Concat(Matrix3X3.CreateTranslation(-(float)firstRect.TopLeft.X, -(float)firstRect.TopLeft.Y));
+        Matrix3X3 matrix = Matrix3X3.Identity;
+        
+        if (!corners.IsRect)
+        {
+            RectD firstRect = RectD.FromCenterAndSize(firstCenter, firstSize);
+            matrix = OperationHelper.CreateMatrixFromPoints(corners, firstSize);
+            matrix = matrix.Concat(
+                Matrix3X3.CreateTranslation(-(float)firstRect.TopLeft.X, -(float)firstRect.TopLeft.Y));
+        }
+        else
+        {
+            firstCenter = data.Center;
+            firstSize = data.Size;
+            
+            if(corners.RectRotation != 0)
+                matrix = Matrix3X3.CreateRotation((float)corners.RectRotation, (float)firstCenter.X, (float)firstCenter.Y);
+        }
 
         RectangleVectorData newData = new RectangleVectorData(firstCenter, firstSize)
         {
-            StrokeColor = data.StrokeColor, FillColor = data.FillColor, StrokeWidth = data.StrokeWidth,
+            StrokeColor = data.StrokeColor,
+            FillColor = data.FillColor,
+            StrokeWidth = data.StrokeWidth,
             TransformationMatrix = matrix
         };
 

+ 1 - 1
src/PixiEditor/Models/Handlers/IToolsHandler.cs

@@ -21,7 +21,7 @@ internal interface IToolsHandler : IHandler
     public bool EnableSharedToolbar { get; set; }
     public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
     public void SetupTools(IServiceProvider services, ToolSetsConfig toolSetConfig);
-    public void SetupToolsTooltipShortcuts(IServiceProvider services);
+    public void SetupToolsTooltipShortcuts();
     public void SetActiveTool<T>(bool transient) where T : IToolHandler;
     public void SetActiveTool(Type toolType, bool transient);
     public void ConvertedKeyDownInlet(FilteredKeyEventArgs args);

+ 3 - 0
src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs

@@ -240,6 +240,9 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         if (currentToolSize != null)
         {
             tools.EnableSharedToolbar = false;
+            var eraserTool = tools.GetTool<EraserToolViewModel>();
+            if(eraserTool == null) return;
+            
             var toolSize = tools.GetTool<EraserToolViewModel>().Toolbar.Settings.First(x => x.Name == "ToolSize");
             previousEraseSize = (int)toolSize.Value;
             toolSize.Value = tools.ActiveTool is PenToolViewModel { PixelPerfectEnabled: true }

+ 6 - 3
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -135,11 +135,14 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         UpdateEnabledState();
     }
 
-    public void SetupToolsTooltipShortcuts(IServiceProvider services)
+    public void SetupToolsTooltipShortcuts()
     {
-        foreach (ToolViewModel tool in ActiveToolSet.Tools!)
+        foreach (IToolHandler tool in allTools)
         {
-            tool.Shortcut = Owner.ShortcutController.GetToolShortcut(tool.GetType());
+            if (tool is ToolViewModel toolVm)
+            {
+                toolVm.Shortcut = Owner.ShortcutController.GetToolShortcut(tool.GetType());
+            }
         }
     }
 

+ 2 - 0
src/PixiEditor/ViewModels/Tools/Tools/VectorEllipseToolViewModel.cs

@@ -18,6 +18,8 @@ internal class VectorEllipseToolViewModel : ShapeTool, IVectorEllipseToolHandler
     private string defaultActionDisplay = "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT";
     public override string ToolNameLocalizationKey => "ELLIPSE_TOOL";
 
+    public override bool IsErasable => false;
+
     public VectorEllipseToolViewModel()
     {
         ActionDisplay = defaultActionDisplay;

+ 2 - 0
src/PixiEditor/ViewModels/Tools/Tools/VectorLineToolViewModel.cs

@@ -21,6 +21,8 @@ internal class VectorLineToolViewModel : ShapeTool, IVectorLineToolHandler
 {
     private string defaultActionDisplay = "LINE_TOOL_ACTION_DISPLAY_DEFAULT";
 
+    public override bool IsErasable => false;
+    
     public VectorLineToolViewModel()
     {
         ActionDisplay = defaultActionDisplay;

+ 27 - 2
src/PixiEditor/ViewModels/Tools/Tools/VectorPathToolViewModel.cs

@@ -18,11 +18,16 @@ internal class VectorPathToolViewModel : ShapeTool, IVectorPathToolHandler
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(VectorLayerNode);
     public override LocalizedString Tooltip => new LocalizedString("PATH_TOOL_TOOLTIP", Shortcut);
 
-    public override string DefaultIcon => PixiPerfectIcons.VectorPen; 
+    public override string DefaultIcon => PixiPerfectIcons.VectorPen;
     public override bool StopsLinkedToolOnUse => false;
+    public override bool IsErasable => false;
 
     private bool isActivated;
 
+    private LocalizedString actionDisplayDefault;
+    private LocalizedString actionDisplayCtrl;
+    private LocalizedString actionDisplayAlt;
+
     public VectorPathToolViewModel()
     {
         var fillSetting = Toolbar.GetSetting(nameof(BasicShapeToolbar.Fill));
@@ -30,6 +35,10 @@ internal class VectorPathToolViewModel : ShapeTool, IVectorPathToolHandler
         {
             fillSetting.Value = false;
         }
+        
+        actionDisplayDefault = new LocalizedString("PATH_TOOL_ACTION_DISPLAY");
+        actionDisplayCtrl = new LocalizedString("PATH_TOOL_ACTION_DISPLAY_CTRL");
+        actionDisplayAlt = new LocalizedString("PATH_TOOL_ACTION_DISPLAY_ALT");
     }
 
     public override void UseTool(VecD pos)
@@ -38,7 +47,7 @@ internal class VectorPathToolViewModel : ShapeTool, IVectorPathToolHandler
             ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
 
         if (doc is null || isActivated) return;
-        
+
         if (!doc.PathOverlayViewModel.IsActive)
         {
             doc?.Tools.UseVectorPathTool();
@@ -46,6 +55,22 @@ internal class VectorPathToolViewModel : ShapeTool, IVectorPathToolHandler
         }
     }
 
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    {
+        if (ctrlIsDown)
+        {
+            ActionDisplay = actionDisplayCtrl;
+        }
+        else if (altIsDown)
+        {
+            ActionDisplay = actionDisplayAlt;
+        }
+        else
+        {
+            ActionDisplay = actionDisplayDefault;
+        }
+    }
+
     public override void OnSelected(bool restoring)
     {
         if (restoring) return;

+ 4 - 3
src/PixiEditor/ViewModels/Tools/Tools/VectorRectangleToolViewModel.cs

@@ -17,6 +17,7 @@ internal class VectorRectangleToolViewModel : ShapeTool, IVectorRectangleToolHan
 {
     private string defaultActionDisplay = "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT";
     public override string ToolNameLocalizationKey => "RECTANGLE_TOOL";
+    public override bool IsErasable => false;
 
     public VectorRectangleToolViewModel()
     {
@@ -53,17 +54,17 @@ internal class VectorRectangleToolViewModel : ShapeTool, IVectorRectangleToolHan
     public override void OnSelected(bool restoring)
     {
         if (restoring) return;
-        
+
         var document = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
         var layer = document.SelectedStructureMember;
-        if (layer is IVectorLayerHandler vectorLayer && 
+        if (layer is IVectorLayerHandler vectorLayer &&
             vectorLayer.GetShapeData(document.AnimationDataViewModel.ActiveFrameTime) is IReadOnlyRectangleData)
         {
             ShapeCorners corners = vectorLayer.TransformationCorners;
             ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument.TransformViewModel.ShowTransform(
                 DocumentTransformMode.Scale_Rotate_Shear_NoPerspective, false, corners, false);
         }
-        
+
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorRectangleTool();
     }
 

+ 1 - 1
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -148,7 +148,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
 
         ShortcutController = new ShortcutController();
 
-        ToolsSubViewModel?.SetupToolsTooltipShortcuts(services);
+        ToolsSubViewModel?.SetupToolsTooltipShortcuts();
 
         SearchSubViewModel = services.GetService<SearchViewModel>();
         

+ 1 - 0
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -60,6 +60,7 @@ internal class ViewportOverlays
         BindMouseOverlayPointer();
         
         vectorPathOverlay = new VectorPathOverlay();
+        vectorPathOverlay.IsVisible = false;
         BindVectorPathOverlay();
 
         Viewport.ActiveOverlays.Add(gridLinesOverlay);