Browse Source

Fixed source calculation of snapping

Krzysztof Krysiński 5 tháng trước cách đây
mục cha
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)
         {
             xAxis = string.Empty;
             yAxis = string.Empty;
+            snapSource = null;
             return VecD.Zero;
         }
 
         bool hasXSnap = false;
         bool hasYSnap = false;
         VecD snapDelta = VecD.Zero;
+        snapSource = null;
 
         string snapAxisX = string.Empty;
         string snapAxisY = string.Empty;
@@ -198,6 +200,7 @@ public class SnappingController
 
             if (snapX is not null && !hasXSnap)
             {
+                snapSource = new VecD(point.X, point.Y);
                 snapDelta += new VecD(snapX.Value - point.X, 0);
                 snapAxisX = newSnapAxisX;
                 hasXSnap = true;
@@ -205,6 +208,7 @@ public class SnappingController
 
             if (snapY is not null && !hasYSnap)
             {
+                snapSource = new VecD(snapSource?.X ?? point.X, point.Y);
                 snapDelta += new VecD(0, snapY.Value - point.Y);
                 snapAxisY = newSnapAxisY;
                 hasYSnap = true;
@@ -286,7 +290,7 @@ public class SnappingController
 
         double? closestX = closestXAxis != string.Empty ? snapDelta.X : null;
         double? closestY = closestYAxis != string.Empty ? snapDelta.Y : null;
-        
+
         VecD? xIntersect = null;
         if (closestX != null)
         {
@@ -316,13 +320,13 @@ public class SnappingController
             if (Math.Abs(xIntersect.Value.X - yIntersect.Value.X) < float.Epsilon
                 && Math.Abs(xIntersect.Value.Y - yIntersect.Value.Y) < float.Epsilon)
             {
-                if(IsWithinSnapDistance(xIntersect.Value, pos))
+                if (IsWithinSnapDistance(xIntersect.Value, pos))
                 {
                     xAxis = closestXAxis;
                     yAxis = closestYAxis;
                     return xIntersect.Value;
                 }
-                
+
                 xAxis = string.Empty;
                 yAxis = string.Empty;
                 return pos;
@@ -344,7 +348,7 @@ public class SnappingController
                 yAxis = closestYAxis;
                 return yIntersect.Value;
             }
-            
+
             xAxis = string.Empty;
             yAxis = string.Empty;
             return pos;
@@ -376,7 +380,27 @@ public class SnappingController
         HorizontalSnapPoints[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)
     {
         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 snapDelta =
-            SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX, out string snapAxisY);
+            SnappingController.GetSnapDeltaForPoints(pointsToTest, out string snapAxisX, out string snapAxisY, out _);
 
         return ((snapAxisX, snapAxisY), snapDelta);
     }

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

@@ -8,6 +8,7 @@ using Drawie.Numerics;
 using Point = Avalonia.Point;
 
 namespace PixiEditor.Views.Overlays.TransformOverlay;
+
 internal static class TransformHelper
 {
     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;
     }
+
     public static double FindSnappingAngle(ShapeCorners corners, double desiredAngle)
     {
         var desTop = (corners.TopLeft - corners.TopRight).Rotate(desiredAngle).Angle;
@@ -106,7 +108,7 @@ internal static class TransformHelper
             GetAnchorPosition(corners, Anchor.Bottom),
             GetAnchorPosition(corners, Anchor.Left),
             GetAnchorPosition(corners, Anchor.Right)
-            );
+        );
         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 topRight = corners.TopRight;
@@ -281,8 +284,10 @@ internal static class TransformHelper
     public static VecD GetHandlePos(ShapeCorners corners, double zoomboxScale, VecD size)
     {
         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);
     }
 
@@ -324,4 +329,26 @@ internal static class TransformHelper
         double[] cardinals = { 0, Math.PI / 2, Math.PI, 3 * Math.PI / 2, 2 * Math.PI };
         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()
     {
         topLeftHandle = new AnchorHandle(this);
-        topLeftHandle.Name = "TL";
         topRightHandle = new AnchorHandle(this);
         bottomLeftHandle = new AnchorHandle(this);
         bottomRightHandle = new AnchorHandle(this);
@@ -752,8 +751,7 @@ internal class TransformOverlay : Overlay
         if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
             ActionCompleted.Execute(null);
 
-        SnappingController.HighlightedXAxis = string.Empty;
-        SnappingController.HighlightedYAxis = string.Empty;
+        HighlightSnappedAxis(null, null);
         IsSizeBoxEnabled = false;
     }
 
@@ -794,8 +792,7 @@ internal class TransformOverlay : Overlay
 
         VecD snapDelta = snapDeltaResult.Delta;
 
-        SnappingController.HighlightedXAxis = snapDeltaResult.SnapAxisXName;
-        SnappingController.HighlightedYAxis = snapDeltaResult.SnapAxisYName;
+        HighlightSnappedAxis(snapDeltaResult.SnapAxisXName, snapDeltaResult.SnapAxisYName, snapDeltaResult.SnapSource);
 
         VecD from = originOnStartMove;
 
@@ -828,13 +825,20 @@ internal class TransformOverlay : Overlay
         VecD[] pointsToTest = new VecD[]
         {
             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,
-            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)
@@ -898,9 +902,9 @@ internal class TransformOverlay : Overlay
                 InternalState.ProportionalAngle2, cornersOnStartAnchorDrag, targetPos,
                 ScaleFromCenter,
                 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)
             {
@@ -980,12 +984,17 @@ internal class TransformOverlay : Overlay
             ((Anchor)capturedAnchor, SideFreedom, InternalState.ProportionalAngle1,
                 InternalState.ProportionalAngle2, cornersOnStartAnchorDrag, targetPos + snapped.Delta,
                 ScaleFromCenter,
-                SnappingController, out string snapX, out string snapY);
+                SnappingController, out string snapX, out string snapY, out _);
 
             string finalSnapX = snapped.SnapAxisXName ?? snapX;
             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)
             {
@@ -1119,7 +1128,7 @@ internal class TransformOverlay : Overlay
         VecD[] pointsToTest = new VecD[] { anchor };
 
         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
         VecD snapPoint = anchor + snapDelta;
@@ -1136,7 +1145,10 @@ internal class TransformOverlay : Overlay
             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)
@@ -1177,15 +1189,19 @@ internal class TransformOverlay : Overlay
         VecD[] pointsToTest = new VecD[] { anchorPos };
 
         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.HighlightedYAxis = snapAxisYName;
+        SnappingController.HighlightedPoint = snapSource;
     }
 
     private void UpdateOriginPos()
@@ -1237,8 +1253,7 @@ internal class TransformOverlay : Overlay
 
         IsSizeBoxEnabled = false;
 
-        SnappingController.HighlightedXAxis = string.Empty;
-        SnappingController.HighlightedYAxis = string.Empty;
+        HighlightSnappedAxis(null, null);
     }
 
     private Handle? GetSnapHandleOfOrigin()
@@ -1292,4 +1307,5 @@ struct SnapData
     public VecD Delta { get; set; }
     public string SnapAxisXName { 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
     (Anchor targetCorner, TransformCornerFreedom freedom, double propAngle1, double propAngle2, ShapeCorners corners,
         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))
             throw new ArgumentException($"{targetCorner} is not a corner");
@@ -21,6 +21,7 @@ internal static class TransformUpdateHelper
         if (freedom == TransformCornerFreedom.Locked)
         {
             snapX = snapY = "";
+            snapPoint = null;
             return corners;
         }
 
@@ -32,6 +33,7 @@ internal static class TransformUpdateHelper
             VecD oppositePos = TransformHelper.GetAnchorPosition(corners, opposite);
 
             snapX = snapY = "";
+            snapPoint = oppositePos;
 
             // constrain desired pos to a "propotional" diagonal line if needed
             if (freedom == TransformCornerFreedom.ScaleProportionally && corners.IsRect)
@@ -43,6 +45,7 @@ internal static class TransformUpdateHelper
                 if (snappingController is not null)
                 {
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
             }
             else if (freedom == TransformCornerFreedom.ScaleProportionally)
@@ -53,6 +56,7 @@ internal static class TransformUpdateHelper
                 if (snappingController is not null)
                 {
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
             }
             else
@@ -60,6 +64,7 @@ internal static class TransformUpdateHelper
                 if (snappingController is not null)
                 {
                     desiredPos = snappingController.GetSnapPoint(desiredPos, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
             }
 
@@ -138,6 +143,7 @@ internal static class TransformUpdateHelper
         if (freedom == TransformCornerFreedom.Free)
         {
             snapX = snapY = "";
+            snapPoint = null;
             ShapeCorners newCorners = TransformHelper.UpdateCorner(corners, targetCorner, desiredPos);
             return newCorners.IsLegal ? newCorners : null;
         }
@@ -189,12 +195,13 @@ internal static class TransformUpdateHelper
     public static ShapeCorners? UpdateShapeFromSide
     (Anchor targetSide, TransformSideFreedom freedom, double propAngle1, double propAngle2, ShapeCorners corners,
         VecD desiredPos, bool scaleFromCenter, SnappingController? snappingController, out string snapX,
-        out string snapY)
+        out string snapY, out VecD? snapPoint)
     {
         if (!TransformHelper.IsSide(targetSide))
             throw new ArgumentException($"{targetSide} is not a side");
 
         snapX = snapY = "";
+        snapPoint = null;
 
         if (freedom == TransformSideFreedom.Locked)
             return corners;
@@ -214,6 +221,7 @@ internal static class TransformUpdateHelper
             {
                 VecD scaleDirection = corners.RectCenter - oppositePos;
                 desiredPos = snappingController.GetSnapPoint(desiredPos, scaleDirection, out snapX, out snapY);
+                snapPoint = desiredPos;
             }
 
             double scalingFactor = (desiredPos - oppositePos) * direction;
@@ -304,6 +312,7 @@ internal static class TransformUpdateHelper
                 {
                     VecD direction = corners.RectCenter - desiredPos;
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
             }
             else if (freedom == TransformSideFreedom.Stretch)
@@ -318,6 +327,7 @@ internal static class TransformUpdateHelper
                 {
                     VecD direction = desiredPos - targetPos;
                     desiredPos = snappingController.GetSnapPoint(desiredPos, direction, out snapX, out snapY);
+                    snapPoint = desiredPos;
                 }
             }