Browse Source

Added origin snapping for origin handle

Krzysztof Krysiński 1 year ago
parent
commit
0858a96373

+ 1 - 2
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/Handle.cs

@@ -26,7 +26,6 @@ public abstract class Handle : IHandle
     public event Action<Handle> OnRelease;
     public event Action<Handle> OnHover;
     public event Action<Handle> OnExit;
-
     public Cursor? Cursor { get; set; }
 
     private bool isPressed;
@@ -102,7 +101,7 @@ public abstract class Handle : IHandle
         }
     }
 
-    private void OnPointerMoved(object? sender, PointerEventArgs e)
+    protected virtual void OnPointerMoved(object? sender, PointerEventArgs e)
     {
         VecD pos = TransformHelper.ToVecD(e.GetPosition(Owner));
         VecD handlePos = Position;

+ 3 - 1
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/OriginAnchor.cs

@@ -1,4 +1,5 @@
-using Avalonia.Controls;
+using Avalonia;
+using Avalonia.Controls;
 using Avalonia.Media;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -8,6 +9,7 @@ namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 public class OriginAnchor : Handle
 {
     public IPen? SecondaryHandlePen { get; set; } = new Pen(Brushes.White, 1);
+
     public OriginAnchor(Control owner, VecD position) : base(owner, position)
     {
 

+ 106 - 101
src/PixiEditor.AvaloniaUI/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -138,30 +138,26 @@ internal class TransformOverlay : Overlay
     private Pen blackFreqDashedPen = new Pen(Brushes.Black, 1) { DashStyle = new DashStyle(new double[] { 2, 2 }, 0) };
     private Pen whiteFreqDashedPen = new Pen(Brushes.White, 1) { DashStyle = new DashStyle(new double[] { 2, 2 }, 2) };
 
-    private TransformHandle moveHandle;
-    private RectangleHandle topLeftHandle;
-    private RectangleHandle topRightHandle;
-    private RectangleHandle bottomLeftHandle;
-    private RectangleHandle bottomRightHandle;
-    private RectangleHandle topHandle;
-    private RectangleHandle bottomHandle;
-    private RectangleHandle leftHandle;
-    private RectangleHandle rightHandle;
+    private AnchorHandle topLeftHandle;
+    private AnchorHandle topRightHandle;
+    private AnchorHandle bottomLeftHandle;
+    private AnchorHandle bottomRightHandle;
+    private AnchorHandle topHandle;
+    private AnchorHandle bottomHandle;
+    private AnchorHandle leftHandle;
+    private AnchorHandle rightHandle;
+    private RectangleHandle centerHandle;
     private OriginAnchor originHandle;
 
     private Dictionary<Handle, Anchor> anchorMap = new();
+    private List<Handle> snapPoints = new();
 
     private Geometry rotateCursorGeometry = Handle.GetHandleGeometry("RotateHandle");
 
     private Point lastPointerPos;
-    private IPointer? capturedPointer;
 
     public TransformOverlay()
     {
-        moveHandle = new TransformHandle(this, VecD.Zero);
-        moveHandle.OnDrag += MoveHandleOnDrag;
-        moveHandle.OnRelease += OnMoveHandleReleased;
-
         topLeftHandle = new AnchorHandle(this, VecD.Zero);
         topRightHandle = new AnchorHandle(this, VecD.Zero);
         bottomLeftHandle = new AnchorHandle(this, VecD.Zero);
@@ -170,6 +166,8 @@ internal class TransformOverlay : Overlay
         bottomHandle = new AnchorHandle(this, VecD.Zero);
         leftHandle = new AnchorHandle(this, VecD.Zero);
         rightHandle = new AnchorHandle(this, VecD.Zero);
+        centerHandle = new RectangleHandle(this, VecD.Zero);
+        centerHandle.Size = rightHandle.Size;
 
         originHandle = new(this, VecD.Zero)
         {
@@ -177,7 +175,6 @@ internal class TransformOverlay : Overlay
         };
 
         AddHandle(originHandle);
-        AddHandle(moveHandle);
         AddHandle(topLeftHandle);
         AddHandle(topRightHandle);
         AddHandle(bottomLeftHandle);
@@ -186,6 +183,7 @@ internal class TransformOverlay : Overlay
         AddHandle(bottomHandle);
         AddHandle(leftHandle);
         AddHandle(rightHandle);
+        AddHandle(centerHandle);
 
         anchorMap.Add(topLeftHandle, Anchor.TopLeft);
         anchorMap.Add(topRightHandle, Anchor.TopRight);
@@ -197,6 +195,8 @@ internal class TransformOverlay : Overlay
         anchorMap.Add(rightHandle, Anchor.Right);
         anchorMap.Add(originHandle, Anchor.Origin);
 
+        ForAllHandles<RectangleHandle>(snapPoints.Add);
+
         ForAllHandles<AnchorHandle>(x =>
         {
             x.OnPress += OnAnchorHandlePressed;
@@ -205,8 +205,6 @@ internal class TransformOverlay : Overlay
 
         originHandle.OnPress += OnAnchorHandlePressed;
         originHandle.OnRelease += OnAnchorHandleReleased;
-
-        moveHandle.OnPress += OnMoveHandlePressed;
     }
 
     public override void Render(DrawingContext drawingContext)
@@ -240,17 +238,6 @@ internal class TransformOverlay : Overlay
         if (LockRotation)
             return;
 
-        Span<Point> points = stackalloc Point[]
-        {
-            TransformHelper.ToPoint(Corners.TopLeft),
-            TransformHelper.ToPoint(Corners.TopRight),
-            TransformHelper.ToPoint(Corners.BottomLeft),
-            TransformHelper.ToPoint(Corners.BottomRight),
-            TransformHelper.ToPoint((Corners.TopLeft + Corners.TopRight) / 2),
-            TransformHelper.ToPoint((Corners.TopLeft + Corners.BottomLeft) / 2),
-            TransformHelper.ToPoint((Corners.BottomRight + Corners.TopRight) / 2),
-            TransformHelper.ToPoint((Corners.BottomRight + Corners.BottomLeft) / 2),
-        };
         /*double ellipseSize = (TransformHelper.AnchorSize * anchorSizeMultiplierForRotation - 2) / (ZoomboxScale * 2);
         foreach (var point in points)
         {
@@ -291,6 +278,7 @@ internal class TransformOverlay : Overlay
 
         // corner anchors
 
+        centerHandle.Position = VecD.Zero;
         topLeftHandle.Position = topLeft;
         topRightHandle.Position = topRight;
         bottomLeftHandle.Position = bottomLeft;
@@ -299,7 +287,7 @@ internal class TransformOverlay : Overlay
         bottomHandle.Position = bottom;
         leftHandle.Position = left;
         rightHandle.Position = right;
-        originHandle.Position = origin;
+        originHandle.Position = InternalState.Origin;
 
         topLeftHandle.Draw(context);
         topRightHandle.Draw(context);
@@ -311,10 +299,11 @@ internal class TransformOverlay : Overlay
         rightHandle.Draw(context);
         originHandle.Draw(context);
 
-        // move handle
-        VecD handlePos = TransformHelper.GetHandlePos(corners, zoomboxScale, moveHandle.Size);
-        moveHandle.Position = handlePos;
-        moveHandle.Draw(context);
+        if (capturedAnchor == Anchor.Origin)
+        {
+            centerHandle.Position = Corners.RectCenter;
+            centerHandle.Draw(context);
+        }
 
         // rotate cursor
         context.DrawGeometry(Brushes.White, blackPen, rotateCursorGeometry);
@@ -326,14 +315,6 @@ internal class TransformOverlay : Overlay
         rotateCursorGeometry.Transform = new ScaleTransform(0, 0);
     }
 
-    private void OnMoveHandlePressed(Handle source, VecD position)
-    {
-        isMoving = true;
-        mousePosOnStartMove = position;
-        originOnStartMove = InternalState.Origin;
-        cornersOnStartMove = Corners;
-    }
-
     private void OnAnchorHandlePressed(Handle source, VecD position)
     {
         capturedAnchor = anchorMap[source];
@@ -349,7 +330,8 @@ internal class TransformOverlay : Overlay
             return;
 
         VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
-        if (!CanRotate(pos))
+
+        if (!CanRotate(pos) && Handles.All(x => !x.IsWithinHandle(x.Position, pos, ZoomboxScale)))
         {
             isMoving = true;
             mousePosOnStartMove = pos;
@@ -370,45 +352,6 @@ internal class TransformOverlay : Overlay
         }
         
         e.Pointer.Capture(this);
-        capturedPointer = e.Pointer;
-    }
-
-    private bool CanRotate(VecD mousePos)
-    {
-        return !Corners.IsPointInside(mousePos) && Handles.All(x => !x.IsWithinHandle(x.Position, mousePos, ZoomboxScale));
-    }
-
-    private bool UpdateRotationCursor(VecD mousePos)
-    {
-        if ((!CanRotate(mousePos) && !isRotating) || LockRotation)
-        {
-            rotateCursorGeometry.Transform = new ScaleTransform(0, 0);
-            return false;
-        }
-
-        var matrix = new TranslateTransform(mousePos.X, mousePos.Y).Value;
-        matrix = matrix.RotateAt((mousePos - InternalState.Origin).Angle * 180 / Math.PI - 90, mousePos.X, mousePos.Y);
-        matrix = matrix.ScaleAt(8 / ZoomboxScale, 8 / ZoomboxScale, mousePos.X, mousePos.Y);
-        rotateCursorGeometry.Transform = new MatrixTransform(matrix);
-        return true;
-    }
-
-    private void MoveHandleOnDrag(Handle source, VecD position)
-    {
-        VecD delta = position - mousePosOnStartMove;
-
-        if (Corners.IsSnappedToPixels)
-            delta = delta.Round();
-
-        Corners = new ShapeCorners()
-        {
-            BottomLeft = cornersOnStartMove.BottomLeft + delta,
-            BottomRight = cornersOnStartMove.BottomRight + delta,
-            TopLeft = cornersOnStartMove.TopLeft + delta,
-            TopRight = cornersOnStartMove.TopRight + delta,
-        };
-
-        InternalState = InternalState with { Origin = originOnStartMove + delta };
     }
 
     protected override void OnPointerMoved(PointerEventArgs e)
@@ -416,6 +359,13 @@ internal class TransformOverlay : Overlay
         Cursor finalCursor = new Cursor(StandardCursorType.Arrow);
 
         lastPointerPos = e.GetPosition(this);
+        VecD pos = TransformHelper.ToVecD(lastPointerPos);
+
+        if (isMoving)
+        {
+            HandleTransform(pos);
+            finalCursor = new Cursor(StandardCursorType.DragMove);
+        }
 
         if (capturedAnchor is not null)
         {
@@ -428,17 +378,11 @@ internal class TransformOverlay : Overlay
             finalCursor = new Cursor(StandardCursorType.None);
         }
 
-        VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
         Anchor? anchor = TransformHelper.GetAnchorInPosition(pos, Corners, InternalState.Origin, ZoomboxScale, topLeftHandle.Size);
 
         if (isRotating)
         {
-            finalCursor = new Cursor(StandardCursorType.None);
-            double angle = (mousePosOnStartRotate - InternalState.Origin).CCWAngleTo(pos - InternalState.Origin);
-            if (SnapToAngles)
-                angle = TransformHelper.FindSnappingAngle(cornersOnStartRotate, angle);
-            InternalState = InternalState with { ProportionalAngle1 = propAngle1OnStartRotate + angle, ProportionalAngle2 = propAngle2OnStartRotate + angle, };
-            Corners = TransformUpdateHelper.UpdateShapeFromRotation(cornersOnStartRotate, InternalState.Origin, angle);
+            finalCursor = HandleRotate(pos);
         }
         else if (anchor is not null)
         {
@@ -455,6 +399,59 @@ internal class TransformOverlay : Overlay
         InvalidateVisual();
     }
 
+    private void HandleTransform(VecD pos)
+    {
+        VecD delta = pos - mousePosOnStartMove;
+
+        if (Corners.IsSnappedToPixels)
+            delta = delta.Round();
+
+        Corners = new ShapeCorners()
+        {
+            BottomLeft = cornersOnStartMove.BottomLeft + delta,
+            BottomRight = cornersOnStartMove.BottomRight + delta,
+            TopLeft = cornersOnStartMove.TopLeft + delta,
+            TopRight = cornersOnStartMove.TopRight + delta,
+        };
+
+        InternalState = InternalState with { Origin = originOnStartMove + delta };
+    }
+
+    private Cursor HandleRotate(VecD pos)
+    {
+        Cursor finalCursor;
+        finalCursor = new Cursor(StandardCursorType.None);
+        double angle = (mousePosOnStartRotate - InternalState.Origin).CCWAngleTo(pos - InternalState.Origin);
+        if (SnapToAngles)
+            angle = TransformHelper.FindSnappingAngle(cornersOnStartRotate, angle);
+        InternalState = InternalState with
+        {
+            ProportionalAngle1 = propAngle1OnStartRotate + angle, ProportionalAngle2 = propAngle2OnStartRotate + angle,
+        };
+        Corners = TransformUpdateHelper.UpdateShapeFromRotation(cornersOnStartRotate, InternalState.Origin, angle);
+        return finalCursor;
+    }
+
+    private bool CanRotate(VecD mousePos)
+    {
+        return !Corners.IsPointInside(mousePos) && Handles.All(x => !x.IsWithinHandle(x.Position, mousePos, ZoomboxScale));
+    }
+
+    private bool UpdateRotationCursor(VecD mousePos)
+    {
+        if ((!CanRotate(mousePos) && !isRotating) || LockRotation)
+        {
+            rotateCursorGeometry.Transform = new ScaleTransform(0, 0);
+            return false;
+        }
+
+        var matrix = new TranslateTransform(mousePos.X, mousePos.Y).Value;
+        matrix = matrix.RotateAt((mousePos - InternalState.Origin).Angle * 180 / Math.PI - 90, mousePos.X, mousePos.Y);
+        matrix = matrix.ScaleAt(8 / ZoomboxScale, 8 / ZoomboxScale, mousePos.X, mousePos.Y);
+        rotateCursorGeometry.Transform = new MatrixTransform(matrix);
+        return true;
+    }
+
     private void HandleCapturedAnchorMovement(PointerEventArgs e)
     {
         if (capturedAnchor is null)
@@ -494,23 +491,30 @@ internal class TransformOverlay : Overlay
         }
         else if (capturedAnchor == Anchor.Origin)
         {
-            InternalState = InternalState with { OriginWasManuallyDragged = true, Origin = originOnStartAnchorDrag + pos - mousePosOnStartAnchorDrag, };
+            pos = HandleSnap(pos);
+            InternalState = InternalState with { OriginWasManuallyDragged = true, Origin = pos, };
         }
     }
 
-    private void OnAnchorHandleReleased(Handle source)
+    private VecD HandleSnap(VecD pos)
     {
-        capturedPointer = null;
-        capturedAnchor = null;
+        foreach (var snapPoint in snapPoints)
+        {
+            if (snapPoint == originHandle)
+                continue;
 
-        if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
-            ActionCompleted.Execute(null);
+            if (TransformHelper.IsWithinHandle(snapPoint.Position, pos, ZoomboxScale, topHandle.Size))
+            {
+                return snapPoint.Position;
+            }
+        }
+
+        return originOnStartAnchorDrag + pos - mousePosOnStartAnchorDrag;
     }
 
-    private void OnMoveHandleReleased(Handle source)
+    private void OnAnchorHandleReleased(Handle source)
     {
-        isMoving = false;
-        capturedPointer = null;
+        capturedAnchor = null;
 
         if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
             ActionCompleted.Execute(null);
@@ -526,14 +530,15 @@ internal class TransformOverlay : Overlay
         {
             isRotating = false;
             e.Pointer.Capture(null);
-            capturedPointer = null;
             Cursor = new Cursor(StandardCursorType.Arrow);
             var pos = TransformHelper.ToVecD(e.GetPosition(this));
             UpdateRotationCursor(pos);
-
-            if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
-                ActionCompleted.Execute(null);
         }
+
+        isMoving = false;
+
+        if (ActionCompleted is not null && ActionCompleted.CanExecute(null))
+            ActionCompleted.Execute(null);
     }
 
     private static void OnRequestedCorners(AvaloniaPropertyChangedEventArgs<ShapeCorners> args)