Browse Source

Fixed source calculation of snapping

Krzysztof Krysiński 5 months ago
parent
commit
7eb0ad6430

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit ea188f49d32efa4875d5ae292d80759e85af5945
+Subproject commit 2bae841cdf55369fc483bc8d007d1d950a838ec3

+ 30 - 6
src/PixiEditor/Models/Controllers/InputDevice/SnappingController.cs

@@ -175,18 +175,20 @@ public class SnappingController
         }
         }
     }
     }
 
 
-    public VecD GetSnapDeltaForPoints(VecD[] points, out string xAxis, out string yAxis)
+    public VecD GetSnapDeltaForPoints(VecD[] points, out string xAxis, out string yAxis, out VecD? snapSource)
     {
     {
         if (!SnappingEnabled)
         if (!SnappingEnabled)
         {
         {
             xAxis = string.Empty;
             xAxis = string.Empty;
             yAxis = string.Empty;
             yAxis = string.Empty;
+            snapSource = null;
             return VecD.Zero;
             return VecD.Zero;
         }
         }
 
 
         bool hasXSnap = false;
         bool hasXSnap = false;
         bool hasYSnap = false;
         bool hasYSnap = false;
         VecD snapDelta = VecD.Zero;
         VecD snapDelta = VecD.Zero;
+        snapSource = null;
 
 
         string snapAxisX = string.Empty;
         string snapAxisX = string.Empty;
         string snapAxisY = string.Empty;
         string snapAxisY = string.Empty;
@@ -198,6 +200,7 @@ public class SnappingController
 
 
             if (snapX is not null && !hasXSnap)
             if (snapX is not null && !hasXSnap)
             {
             {
+                snapSource = new VecD(point.X, point.Y);
                 snapDelta += new VecD(snapX.Value - point.X, 0);
                 snapDelta += new VecD(snapX.Value - point.X, 0);
                 snapAxisX = newSnapAxisX;
                 snapAxisX = newSnapAxisX;
                 hasXSnap = true;
                 hasXSnap = true;
@@ -205,6 +208,7 @@ public class SnappingController
 
 
             if (snapY is not null && !hasYSnap)
             if (snapY is not null && !hasYSnap)
             {
             {
+                snapSource = new VecD(snapSource?.X ?? point.X, point.Y);
                 snapDelta += new VecD(0, snapY.Value - point.Y);
                 snapDelta += new VecD(0, snapY.Value - point.Y);
                 snapAxisY = newSnapAxisY;
                 snapAxisY = newSnapAxisY;
                 hasYSnap = true;
                 hasYSnap = true;
@@ -286,7 +290,7 @@ public class SnappingController
 
 
         double? closestX = closestXAxis != string.Empty ? snapDelta.X : null;
         double? closestX = closestXAxis != string.Empty ? snapDelta.X : null;
         double? closestY = closestYAxis != string.Empty ? snapDelta.Y : null;
         double? closestY = closestYAxis != string.Empty ? snapDelta.Y : null;
-        
+
         VecD? xIntersect = null;
         VecD? xIntersect = null;
         if (closestX != null)
         if (closestX != null)
         {
         {
@@ -316,13 +320,13 @@ public class SnappingController
             if (Math.Abs(xIntersect.Value.X - yIntersect.Value.X) < float.Epsilon
             if (Math.Abs(xIntersect.Value.X - yIntersect.Value.X) < float.Epsilon
                 && Math.Abs(xIntersect.Value.Y - yIntersect.Value.Y) < float.Epsilon)
                 && Math.Abs(xIntersect.Value.Y - yIntersect.Value.Y) < float.Epsilon)
             {
             {
-                if(IsWithinSnapDistance(xIntersect.Value, pos))
+                if (IsWithinSnapDistance(xIntersect.Value, pos))
                 {
                 {
                     xAxis = closestXAxis;
                     xAxis = closestXAxis;
                     yAxis = closestYAxis;
                     yAxis = closestYAxis;
                     return xIntersect.Value;
                     return xIntersect.Value;
                 }
                 }
-                
+
                 xAxis = string.Empty;
                 xAxis = string.Empty;
                 yAxis = string.Empty;
                 yAxis = string.Empty;
                 return pos;
                 return pos;
@@ -344,7 +348,7 @@ public class SnappingController
                 yAxis = closestYAxis;
                 yAxis = closestYAxis;
                 return yIntersect.Value;
                 return yIntersect.Value;
             }
             }
-            
+
             xAxis = string.Empty;
             xAxis = string.Empty;
             yAxis = string.Empty;
             yAxis = string.Empty;
             return pos;
             return pos;
@@ -376,7 +380,27 @@ public class SnappingController
         HorizontalSnapPoints[identifier] = pointFunc;
         HorizontalSnapPoints[identifier] = pointFunc;
         VerticalSnapPoints[identifier] = pointFunc;
         VerticalSnapPoints[identifier] = pointFunc;
     }
     }
-    
+
+    public VecD? GetSnapAxisXPoint(string snapAxisX)
+    {
+        if (HorizontalSnapPoints.TryGetValue(snapAxisX, out Func<VecD> snapPoint))
+        {
+            return snapPoint();
+        }
+
+        return null;
+    }
+
+    public VecD? GetSnapAxisYPoint(string snapAxisY)
+    {
+        if (VerticalSnapPoints.TryGetValue(snapAxisY, out Func<VecD> snapPoint))
+        {
+            return snapPoint();
+        }
+
+        return null;
+    }
+
     private bool IsWithinSnapDistance(VecD snapPoint, VecD pos)
     private bool IsWithinSnapDistance(VecD snapPoint, VecD pos)
     {
     {
         return (snapPoint - pos).LengthSquared < SnapDistance * SnapDistance;
         return (snapPoint - pos).LengthSquared < SnapDistance * SnapDistance;

+ 1 - 1
src/PixiEditor/Views/Overlays/LineToolOverlay/LineToolOverlay.cs

@@ -330,7 +330,7 @@ internal class LineToolOverlay : Overlay
         VecD[] pointsToTest = new VecD[] { center + delta, originalStart + delta, originalEnd + delta, };
         VecD[] pointsToTest = new VecD[] { center + delta, originalStart + delta, originalEnd + delta, };
 
 
         VecD snapDelta =
         VecD snapDelta =
-            SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX, out string snapAxisY);
+            SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX, out string snapAxisY, out _);
 
 
         return ((snapAxisX, snapAxisY), snapDelta);
         return ((snapAxisX, snapAxisY), snapDelta);
     }
     }

+ 31 - 4
src/PixiEditor/Views/Overlays/TransformOverlay/TransformHelper.cs

@@ -8,6 +8,7 @@ using Drawie.Numerics;
 using Point = Avalonia.Point;
 using Point = Avalonia.Point;
 
 
 namespace PixiEditor.Views.Overlays.TransformOverlay;
 namespace PixiEditor.Views.Overlays.TransformOverlay;
+
 internal static class TransformHelper
 internal static class TransformHelper
 {
 {
     public static RectD ToHandleRect(VecD pos, VecD size, double zoomboxScale)
     public static RectD ToHandleRect(VecD pos, VecD size, double zoomboxScale)
@@ -77,6 +78,7 @@ internal static class TransformHelper
     {
     {
         return Math.Round(angle * 8 / (Math.PI * 2)) * (Math.PI * 2) / 8;
         return Math.Round(angle * 8 / (Math.PI * 2)) * (Math.PI * 2) / 8;
     }
     }
+
     public static double FindSnappingAngle(ShapeCorners corners, double desiredAngle)
     public static double FindSnappingAngle(ShapeCorners corners, double desiredAngle)
     {
     {
         var desTop = (corners.TopLeft - corners.TopRight).Rotate(desiredAngle).Angle;
         var desTop = (corners.TopLeft - corners.TopRight).Rotate(desiredAngle).Angle;
@@ -106,7 +108,7 @@ internal static class TransformHelper
             GetAnchorPosition(corners, Anchor.Bottom),
             GetAnchorPosition(corners, Anchor.Bottom),
             GetAnchorPosition(corners, Anchor.Left),
             GetAnchorPosition(corners, Anchor.Left),
             GetAnchorPosition(corners, Anchor.Right)
             GetAnchorPosition(corners, Anchor.Right)
-            );
+        );
         return maybeOrigin ?? corners.TopLeft.Lerp(corners.BottomRight, 0.5);
         return maybeOrigin ?? corners.TopLeft.Lerp(corners.BottomRight, 0.5);
     }
     }
 
 
@@ -238,7 +240,8 @@ internal static class TransformHelper
         };
         };
     }
     }
 
 
-    public static Anchor? GetAnchorInPosition(VecD pos, ShapeCorners corners, VecD origin, double zoomboxScale, VecD size)
+    public static Anchor? GetAnchorInPosition(VecD pos, ShapeCorners corners, VecD origin, double zoomboxScale,
+        VecD size)
     {
     {
         VecD topLeft = corners.TopLeft;
         VecD topLeft = corners.TopLeft;
         VecD topRight = corners.TopRight;
         VecD topRight = corners.TopRight;
@@ -281,8 +284,10 @@ internal static class TransformHelper
     public static VecD GetHandlePos(ShapeCorners corners, double zoomboxScale, VecD size)
     public static VecD GetHandlePos(ShapeCorners corners, double zoomboxScale, VecD size)
     {
     {
         VecD max = new(
         VecD max = new(
-            Math.Max(Math.Max(corners.TopLeft.X, corners.TopRight.X), Math.Max(corners.BottomLeft.X, corners.BottomRight.X)),
-            Math.Max(Math.Max(corners.TopLeft.Y, corners.TopRight.Y), Math.Max(corners.BottomLeft.Y, corners.BottomRight.Y)));
+            Math.Max(Math.Max(corners.TopLeft.X, corners.TopRight.X),
+                Math.Max(corners.BottomLeft.X, corners.BottomRight.X)),
+            Math.Max(Math.Max(corners.TopLeft.Y, corners.TopRight.Y),
+                Math.Max(corners.BottomLeft.Y, corners.BottomRight.Y)));
         return max + new VecD(size.X / zoomboxScale, size.Y / zoomboxScale);
         return max + new VecD(size.X / zoomboxScale, size.Y / zoomboxScale);
     }
     }
 
 
@@ -324,4 +329,26 @@ internal static class TransformHelper
         double[] cardinals = { 0, Math.PI / 2, Math.PI, 3 * Math.PI / 2, 2 * Math.PI };
         double[] cardinals = { 0, Math.PI / 2, Math.PI, 3 * Math.PI / 2, 2 * Math.PI };
         return cardinals.Any(cardinal => Math.Abs(normalized - cardinal) < threshold);
         return cardinals.Any(cardinal => Math.Abs(normalized - cardinal) < threshold);
     }
     }
+
+    public static VecD? GetClosestAnchorToPoint(VecD point, ShapeCorners corners)
+    {
+        var distances = new Dictionary<Anchor, double>
+        {
+            { Anchor.TopLeft, (point - corners.TopLeft).Length },
+            { Anchor.TopRight, (point - corners.TopRight).Length },
+            { Anchor.BottomLeft, (point - corners.BottomLeft).Length },
+            { Anchor.BottomRight, (point - corners.BottomRight).Length },
+            { Anchor.Left, (point - corners.LeftCenter).Length },
+            { Anchor.Right, (point - corners.RightCenter).Length },
+            { Anchor.Top, (point - corners.TopCenter).Length },
+            { Anchor.Bottom, (point - corners.BottomCenter).Length },
+        };
+
+        var ordered = distances.OrderBy(pair => pair.Value).ToList();
+        if (!ordered.Any())
+            return null;
+
+        var anchor = ordered.First().Key;
+        return GetAnchorPosition(corners, anchor);
+    }
 }
 }

+ 35 - 19
src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -301,7 +301,6 @@ internal class TransformOverlay : Overlay
     public TransformOverlay()
     public TransformOverlay()
     {
     {
         topLeftHandle = new AnchorHandle(this);
         topLeftHandle = new AnchorHandle(this);
-        topLeftHandle.Name = "TL";
         topRightHandle = new AnchorHandle(this);
         topRightHandle = new AnchorHandle(this);
         bottomLeftHandle = new AnchorHandle(this);
         bottomLeftHandle = new AnchorHandle(this);
         bottomRightHandle = new AnchorHandle(this);
         bottomRightHandle = new AnchorHandle(this);
@@ -752,8 +751,7 @@ internal class TransformOverlay : Overlay
         if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
         if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
             ActionCompleted.Execute(null);
             ActionCompleted.Execute(null);
 
 
-        SnappingController.HighlightedXAxis = string.Empty;
-        SnappingController.HighlightedYAxis = string.Empty;
+        HighlightSnappedAxis(null, null);
         IsSizeBoxEnabled = false;
         IsSizeBoxEnabled = false;
     }
     }
 
 
@@ -794,8 +792,7 @@ internal class TransformOverlay : Overlay
 
 
         VecD snapDelta = snapDeltaResult.Delta;
         VecD snapDelta = snapDeltaResult.Delta;
 
 
-        SnappingController.HighlightedXAxis = snapDeltaResult.SnapAxisXName;
-        SnappingController.HighlightedYAxis = snapDeltaResult.SnapAxisYName;
+        HighlightSnappedAxis(snapDeltaResult.SnapAxisXName, snapDeltaResult.SnapAxisYName, snapDeltaResult.SnapSource);
 
 
         VecD from = originOnStartMove;
         VecD from = originOnStartMove;
 
 
@@ -828,13 +825,20 @@ internal class TransformOverlay : Overlay
         VecD[] pointsToTest = new VecD[]
         VecD[] pointsToTest = new VecD[]
         {
         {
             rawCorners.RectCenter, rawCorners.TopLeft, rawCorners.TopRight, rawCorners.BottomLeft,
             rawCorners.RectCenter, rawCorners.TopLeft, rawCorners.TopRight, rawCorners.BottomLeft,
-            rawCorners.BottomRight
+            rawCorners.BottomRight, rawCorners.TopCenter, rawCorners.BottomCenter, rawCorners.LeftCenter,
+            rawCorners.RightCenter
         };
         };
 
 
         VecD snapDelta = SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX,
         VecD snapDelta = SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX,
-            out string snapAxisY);
+            out string snapAxisY, out VecD? snapSource);
 
 
-        return new SnapData() { Delta = snapDelta, SnapAxisXName = snapAxisX, SnapAxisYName = snapAxisY };
+        return new SnapData()
+        {
+            Delta = snapDelta,
+            SnapSource = snapSource + snapDelta,
+            SnapAxisXName = snapAxisX,
+            SnapAxisYName = snapAxisY
+        };
     }
     }
 
 
     private Cursor HandleRotate(VecD pos)
     private Cursor HandleRotate(VecD pos)
@@ -898,9 +902,9 @@ internal class TransformOverlay : Overlay
                 InternalState.ProportionalAngle2, cornersOnStartAnchorDrag, targetPos,
                 InternalState.ProportionalAngle2, cornersOnStartAnchorDrag, targetPos,
                 ScaleFromCenter,
                 ScaleFromCenter,
                 SnappingController,
                 SnappingController,
-                out string snapX, out string snapY);
+                out string snapX, out string snapY, out VecD? snapPoint);
 
 
-            HighlightSnappedAxis(snapX, snapY);
+            HighlightSnappedAxis(snapX, snapY, snapPoint);
 
 
             if (newCorners is not null)
             if (newCorners is not null)
             {
             {
@@ -980,12 +984,17 @@ internal class TransformOverlay : Overlay
             ((Anchor)capturedAnchor, SideFreedom, InternalState.ProportionalAngle1,
             ((Anchor)capturedAnchor, SideFreedom, InternalState.ProportionalAngle1,
                 InternalState.ProportionalAngle2, cornersOnStartAnchorDrag, targetPos + snapped.Delta,
                 InternalState.ProportionalAngle2, cornersOnStartAnchorDrag, targetPos + snapped.Delta,
                 ScaleFromCenter,
                 ScaleFromCenter,
-                SnappingController, out string snapX, out string snapY);
+                SnappingController, out string snapX, out string snapY, out _);
 
 
             string finalSnapX = snapped.SnapAxisXName ?? snapX;
             string finalSnapX = snapped.SnapAxisXName ?? snapX;
             string finalSnapY = snapped.SnapAxisYName ?? snapY;
             string finalSnapY = snapped.SnapAxisYName ?? snapY;
+            VecD? finalSnapPoint = null;
+            if (newCorners.HasValue)
+            {
+                finalSnapPoint = TransformHelper.GetAnchorPosition(newCorners.Value, (Anchor)capturedAnchor);
+            }
 
 
-            HighlightSnappedAxis(finalSnapX, finalSnapY);
+            HighlightSnappedAxis(finalSnapX, finalSnapY, finalSnapPoint);
 
 
             if (newCorners is not null)
             if (newCorners is not null)
             {
             {
@@ -1119,7 +1128,7 @@ internal class TransformOverlay : Overlay
         VecD[] pointsToTest = new VecD[] { anchor };
         VecD[] pointsToTest = new VecD[] { anchor };
 
 
         VecD snapDelta = SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX,
         VecD snapDelta = SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX,
-            out string snapAxisY);
+            out string snapAxisY, out VecD? snapSource);
 
 
         // snap delta is a straight line from the anchor to the snapped point, we need to find intersection between snap point axis and line starting from projectLineStart going through transformed
         // snap delta is a straight line from the anchor to the snapped point, we need to find intersection between snap point axis and line starting from projectLineStart going through transformed
         VecD snapPoint = anchor + snapDelta;
         VecD snapPoint = anchor + snapDelta;
@@ -1136,7 +1145,10 @@ internal class TransformOverlay : Overlay
             snapDelta = VecD.Zero;
             snapDelta = VecD.Zero;
         }
         }
 
 
-        return new SnapData() { Delta = snapDelta, SnapAxisXName = snapAxisX, SnapAxisYName = snapAxisY };
+        return new SnapData()
+        {
+            Delta = snapDelta, SnapSource = snapSource, SnapAxisXName = snapAxisX, SnapAxisYName = snapAxisY
+        };
     }
     }
 
 
     private VecD FindHorizontalIntersection(VecD p1, VecD p2, double y)
     private VecD FindHorizontalIntersection(VecD p1, VecD p2, double y)
@@ -1177,15 +1189,19 @@ internal class TransformOverlay : Overlay
         VecD[] pointsToTest = new VecD[] { anchorPos };
         VecD[] pointsToTest = new VecD[] { anchorPos };
 
 
         VecD snapDelta = SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX,
         VecD snapDelta = SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX,
-            out string snapAxisY);
+            out string snapAxisY, out VecD? snapSource);
 
 
-        return new SnapData() { Delta = snapDelta, SnapAxisXName = snapAxisX, SnapAxisYName = snapAxisY };
+        return new SnapData()
+        {
+            Delta = snapDelta, SnapSource = snapSource, SnapAxisXName = snapAxisX, SnapAxisYName = snapAxisY
+        };
     }
     }
 
 
-    private void HighlightSnappedAxis(string snapAxisXName, string snapAxisYName)
+    private void HighlightSnappedAxis(string snapAxisXName, string snapAxisYName, VecD? snapSource = null)
     {
     {
         SnappingController.HighlightedXAxis = snapAxisXName;
         SnappingController.HighlightedXAxis = snapAxisXName;
         SnappingController.HighlightedYAxis = snapAxisYName;
         SnappingController.HighlightedYAxis = snapAxisYName;
+        SnappingController.HighlightedPoint = snapSource;
     }
     }
 
 
     private void UpdateOriginPos()
     private void UpdateOriginPos()
@@ -1237,8 +1253,7 @@ internal class TransformOverlay : Overlay
 
 
         IsSizeBoxEnabled = false;
         IsSizeBoxEnabled = false;
 
 
-        SnappingController.HighlightedXAxis = string.Empty;
-        SnappingController.HighlightedYAxis = string.Empty;
+        HighlightSnappedAxis(null, null);
     }
     }
 
 
     private Handle? GetSnapHandleOfOrigin()
     private Handle? GetSnapHandleOfOrigin()
@@ -1292,4 +1307,5 @@ struct SnapData
     public VecD Delta { get; set; }
     public VecD Delta { get; set; }
     public string SnapAxisXName { get; set; }
     public string SnapAxisXName { get; set; }
     public string SnapAxisYName { get; set; }
     public string SnapAxisYName { get; set; }
+    public VecD? SnapSource { get; set; }
 }
 }

+ 12 - 2
src/PixiEditor/Views/Overlays/TransformOverlay/TransformUpdateHelper.cs

@@ -13,7 +13,7 @@ internal static class TransformUpdateHelper
     public static ShapeCorners? UpdateShapeFromCorner
     public static ShapeCorners? UpdateShapeFromCorner
     (Anchor targetCorner, TransformCornerFreedom freedom, double propAngle1, double propAngle2, ShapeCorners corners,
     (Anchor targetCorner, TransformCornerFreedom freedom, double propAngle1, double propAngle2, ShapeCorners corners,
         VecD desiredPos, bool scaleFromCenter,
         VecD desiredPos, bool scaleFromCenter,
-        SnappingController? snappingController, out string snapX, out string snapY)
+        SnappingController? snappingController, out string snapX, out string snapY, out VecD? snapPoint)
     {
     {
         if (!TransformHelper.IsCorner(targetCorner))
         if (!TransformHelper.IsCorner(targetCorner))
             throw new ArgumentException($"{targetCorner} is not a corner");
             throw new ArgumentException($"{targetCorner} is not a corner");
@@ -21,6 +21,7 @@ internal static class TransformUpdateHelper
         if (freedom == TransformCornerFreedom.Locked)
         if (freedom == TransformCornerFreedom.Locked)
         {
         {
             snapX = snapY = "";
             snapX = snapY = "";
+            snapPoint = null;
             return corners;
             return corners;
         }
         }
 
 
@@ -32,6 +33,7 @@ internal static class TransformUpdateHelper
             VecD oppositePos = TransformHelper.GetAnchorPosition(corners, opposite);
             VecD oppositePos = TransformHelper.GetAnchorPosition(corners, opposite);
 
 
             snapX = snapY = "";
             snapX = snapY = "";
+            snapPoint = oppositePos;
 
 
             // constrain desired pos to a "propotional" diagonal line if needed
             // constrain desired pos to a "propotional" diagonal line if needed
             if (freedom == TransformCornerFreedom.ScaleProportionally && corners.IsRect)
             if (freedom == TransformCornerFreedom.ScaleProportionally && corners.IsRect)
@@ -43,6 +45,7 @@ internal static class TransformUpdateHelper
                 if (snappingController is not null)
                 if (snappingController is not null)
                 {
                 {
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
                 }
             }
             }
             else if (freedom == TransformCornerFreedom.ScaleProportionally)
             else if (freedom == TransformCornerFreedom.ScaleProportionally)
@@ -53,6 +56,7 @@ internal static class TransformUpdateHelper
                 if (snappingController is not null)
                 if (snappingController is not null)
                 {
                 {
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
                 }
             }
             }
             else
             else
@@ -60,6 +64,7 @@ internal static class TransformUpdateHelper
                 if (snappingController is not null)
                 if (snappingController is not null)
                 {
                 {
                     desiredPos = snappingController.GetSnapPoint(desiredPos, out snapX, out snapY);
                     desiredPos = snappingController.GetSnapPoint(desiredPos, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
                 }
             }
             }
 
 
@@ -138,6 +143,7 @@ internal static class TransformUpdateHelper
         if (freedom == TransformCornerFreedom.Free)
         if (freedom == TransformCornerFreedom.Free)
         {
         {
             snapX = snapY = "";
             snapX = snapY = "";
+            snapPoint = null;
             ShapeCorners newCorners = TransformHelper.UpdateCorner(corners, targetCorner, desiredPos);
             ShapeCorners newCorners = TransformHelper.UpdateCorner(corners, targetCorner, desiredPos);
             return newCorners.IsLegal ? newCorners : null;
             return newCorners.IsLegal ? newCorners : null;
         }
         }
@@ -189,12 +195,13 @@ internal static class TransformUpdateHelper
     public static ShapeCorners? UpdateShapeFromSide
     public static ShapeCorners? UpdateShapeFromSide
     (Anchor targetSide, TransformSideFreedom freedom, double propAngle1, double propAngle2, ShapeCorners corners,
     (Anchor targetSide, TransformSideFreedom freedom, double propAngle1, double propAngle2, ShapeCorners corners,
         VecD desiredPos, bool scaleFromCenter, SnappingController? snappingController, out string snapX,
         VecD desiredPos, bool scaleFromCenter, SnappingController? snappingController, out string snapX,
-        out string snapY)
+        out string snapY, out VecD? snapPoint)
     {
     {
         if (!TransformHelper.IsSide(targetSide))
         if (!TransformHelper.IsSide(targetSide))
             throw new ArgumentException($"{targetSide} is not a side");
             throw new ArgumentException($"{targetSide} is not a side");
 
 
         snapX = snapY = "";
         snapX = snapY = "";
+        snapPoint = null;
 
 
         if (freedom == TransformSideFreedom.Locked)
         if (freedom == TransformSideFreedom.Locked)
             return corners;
             return corners;
@@ -214,6 +221,7 @@ internal static class TransformUpdateHelper
             {
             {
                 VecD scaleDirection = corners.RectCenter - oppositePos;
                 VecD scaleDirection = corners.RectCenter - oppositePos;
                 desiredPos = snappingController.GetSnapPoint(desiredPos, scaleDirection, out snapX, out snapY);
                 desiredPos = snappingController.GetSnapPoint(desiredPos, scaleDirection, out snapX, out snapY);
+                snapPoint = desiredPos;
             }
             }
 
 
             double scalingFactor = (desiredPos - oppositePos) * direction;
             double scalingFactor = (desiredPos - oppositePos) * direction;
@@ -304,6 +312,7 @@ internal static class TransformUpdateHelper
                 {
                 {
                     VecD direction = corners.RectCenter - desiredPos;
                     VecD direction = corners.RectCenter - desiredPos;
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
                 }
             }
             }
             else if (freedom == TransformSideFreedom.Stretch)
             else if (freedom == TransformSideFreedom.Stretch)
@@ -318,6 +327,7 @@ internal static class TransformUpdateHelper
                 {
                 {
                     VecD direction = desiredPos - targetPos;
                     VecD direction = desiredPos - targetPos;
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
                 }
             }
             }