Browse Source

Drag everything, better rotation, better anchor dragging

Equbuxu 3 years ago
parent
commit
256f109028

+ 19 - 0
src/ChunkyImageLib/DataHolders/ShapeCorners.cs

@@ -31,4 +31,23 @@ public struct ShapeCorners
         (TopLeft - TopRight).Cross(TopLeft - BottomLeft) > 0 ?
         RectSize.CCWAngleTo(BottomRight - TopLeft) :
         RectSize.CCWAngleTo(BottomLeft - TopRight);
+    public bool IsPointInside(Vector2d point)
+    {
+        var top = TopLeft - TopRight;
+        var right = TopRight - BottomRight;
+        var bottom = BottomRight - BottomLeft;
+        var left = BottomLeft - TopLeft;
+
+        var deltaTopLeft = point - TopLeft;
+        var deltaTopRight = point - TopRight;
+        var deltaBottomRight = point - BottomRight;
+        var deltaBottomLeft = point - BottomLeft;
+
+        var crossTop = Math.Sign(top.Cross(deltaTopLeft));
+        var crossRight = Math.Sign(right.Cross(deltaTopRight));
+        var crossBottom = Math.Sign(bottom.Cross(deltaBottomRight));
+        var crossLeft = Math.Sign(left.Cross(deltaBottomLeft));
+
+        return crossTop == crossRight && crossTop == crossLeft && crossTop == crossBottom;
+    }
 }

+ 0 - 120
src/PixiEditorPrototype/CustomControls/TransformOverlay/AffineMode.cs

@@ -1,120 +0,0 @@
-namespace PixiEditorPrototype.CustomControls.TransformOverlay;/*
-internal class AffineMode : ITransformMode
-{
-    private TransformOverlay owner;
-
-    public AffineMode(TransformOverlay owner)
-    {
-        this.owner = owner;
-    }
-
-    public void OnAnchorDrag(Vector2d pos, Anchor anchor)
-    {
-        switch (anchor)
-        {
-            case Anchor.Rotation:
-                OnRotationDrag(pos);
-                break;
-            case Anchor.TopLeft:
-            case Anchor.TopRight:
-            case Anchor.BottomLeft:
-            case Anchor.BottomRight:
-                MoveCornerKeepStraight(pos, anchor);
-                break;
-            case Anchor.Top:
-            case Anchor.Left:
-            case Anchor.Right:
-            case Anchor.Bottom:
-                MoveSideKeepStraight(pos, anchor);
-                break;
-        }
-    }
-
-    private void MoveSideKeepStraight(Vector2d pos, Anchor anchor)
-    {
-        Vector2d curPos = AnchorInTransformSpace(anchor);
-        Vector2d newPos = (pos - owner.AffineTransform.Center).Rotate(-owner.AffineTransform.Angle);
-
-        if (anchor is Anchor.Bottom or Anchor.Top)
-            newPos.X = curPos.X;
-        else
-            newPos.Y = curPos.Y;
-
-        Vector2d topLeft = AnchorInTransformSpace(Anchor.TopLeft);
-        Vector2d bottomRight = AnchorInTransformSpace(Anchor.BottomRight);
-        Vector2d delta = newPos - curPos;
-
-        if (anchor is Anchor.Top or Anchor.Left)
-            topLeft += delta;
-        else
-            bottomRight += delta;
-
-        Vector2d newTopLeft = new(Math.Min(topLeft.X, bottomRight.X), Math.Min(topLeft.Y, bottomRight.Y));
-        Vector2d newBottomRight = new(Math.Max(topLeft.X, bottomRight.X), Math.Max(topLeft.Y, bottomRight.Y));
-
-        Vector2d deltaCenter =
-            ((newTopLeft - newBottomRight) / 2 + newBottomRight)
-            .Rotate(owner.AffineTransform.Angle) / 2;
-
-        Vector2d newSize = (newBottomRight - newTopLeft);
-
-        owner.AffineTransform = new AffineTransform(owner.AffineTransform.Center + deltaCenter, newSize, owner.AffineTransform.Angle);
-    }
-
-    private void MoveCornerKeepStraight(Vector2d pos, Anchor anchor)
-    {
-        Vector2d curPos = AnchorInTransformSpace(anchor);
-        Vector2d newPos = (pos - owner.AffineTransform.Center).Rotate(-owner.AffineTransform.Angle);
-
-        Anchor opposite = GetOppositeCorner(anchor);
-        Vector2d oppPos = AnchorInTransformSpace(opposite);
-
-        Vector2d oldSize = curPos - oppPos;
-        Vector2d newSize = newPos - oppPos;
-
-        Vector2d deltaCenter = (newSize - oldSize).Rotate(owner.AffineTransform.Angle) / 2;
-        owner.AffineTransform = new AffineTransform(owner.AffineTransform.Center + deltaCenter, newSize.Abs(), owner.AffineTransform.Angle);
-    }
-
-    private Vector2d AnchorInTransformSpace(Anchor anchor)
-    {
-        var halfSize = owner.AffineTransform.Size / 2;
-        return anchor switch
-        {
-            Anchor.TopLeft => -halfSize,
-            Anchor.TopRight => new(halfSize.X, -halfSize.Y),
-            Anchor.BottomLeft => new(-halfSize.X, halfSize.Y),
-            Anchor.BottomRight => halfSize,
-            Anchor.Left => new(-halfSize.X, 0),
-            Anchor.Right => new(halfSize.X, 0),
-            Anchor.Top => new(0, -halfSize.Y),
-            Anchor.Bottom => new(0, halfSize.Y),
-            _ => throw new System.NotImplementedException(),
-        };
-    }
-
-    private Anchor GetOppositeSide(Anchor side)
-    {
-        return side switch
-        {
-            Anchor.Left => Anchor.Right,
-            Anchor.Right => Anchor.Left,
-            Anchor.Top => Anchor.Bottom,
-            Anchor.Bottom => Anchor.Top,
-            _ => throw new ArgumentException($"{side} is not a side anchor"),
-        };
-    }
-
-    private (Anchor,Anchor) GetCornersOnSide(Anchor side)
-    {
-        return side switch
-        {
-            Anchor.Left => (Anchor.TopLeft, Anchor.BottomLeft),
-            Anchor.Right => (Anchor.TopRight, Anchor.BottomRight),
-            Anchor.Top => (Anchor.TopLeft, Anchor.TopRight),
-            Anchor.Bottom => (Anchor.BottomRight, Anchor.BottomLeft),
-            _ => throw new ArgumentException($"{side} is not a side anchor"),
-        };
-    }
-}
-*/

+ 1 - 1
src/PixiEditorPrototype/CustomControls/TransformOverlay/Anchor.cs

@@ -4,5 +4,5 @@ internal enum Anchor
 {
     TopLeft, TopRight, BottomLeft, BottomRight,
     Top, Left, Right, Bottom,
-    Rotation, Origin
+    Origin
 }

+ 58 - 27
src/PixiEditorPrototype/CustomControls/TransformOverlay/TransformHelper.cs

@@ -6,7 +6,8 @@ using ChunkyImageLib.DataHolders;
 namespace PixiEditorPrototype.CustomControls.TransformOverlay;
 internal static class TransformHelper
 {
-    public const double SideLength = 10;
+    public const double AnchorSize = 10;
+    public const double MoveHandleSize = 16;
 
     private static Pen blackPen = new Pen(Brushes.Black, 1);
     private static Pen blackDashedPen = new Pen(Brushes.Black, 1) { DashStyle = new DashStyle(new double[] { 2, 4 }, 0) };
@@ -14,9 +15,22 @@ internal static class TransformHelper
     private static Pen blackFreqDashedPen = new Pen(Brushes.Black, 1) { DashStyle = new DashStyle(new double[] { 2, 2 }, 0) };
     private static Pen whiteFreqDashedPen = new Pen(Brushes.White, 1) { DashStyle = new DashStyle(new double[] { 2, 2 }, 2) };
 
-    public static Rect ToRect(Vector2d pos, double zoomboxScale)
+    private static PathGeometry handleGeometry = new()
     {
-        double scaled = SideLength / zoomboxScale;
+        FillRule = FillRule.Nonzero,
+        Figures = (PathFigureCollection?)new PathFigureCollectionConverter()
+            .ConvertFrom("M 0.50025839 0 0.4248062 0.12971572 0.34987079 0.25994821 h 0.1002584 V 0.45012906 H 0.25994831 V 0.34987066 L 0.12971577 0.42480604 0 0.5002582 0.12971577 0.57519373 0.25994831 0.65012926 V 0.5498709 H 0.45012919 V 0.74005175 H 0.34987079 L 0.42480619 0.87028439 0.50025839 1 0.57519399 0.87028439 0.65012959 0.74005175 H 0.54987119 V 0.5498709 H 0.74005211 V 0.65012926 L 0.87028423 0.57519358 1 0.5002582 0.87028423 0.42480604 0.74005169 0.34987066 v 0.1002584 H 0.54987077 V 0.25994821 h 0.1002584 L 0.5751938 0.12971572 Z"),
+    };
+
+    public static Rect ToAnchorRect(Vector2d pos, double zoomboxScale)
+    {
+        double scaled = AnchorSize / zoomboxScale;
+        return new Rect(pos.X - scaled / 2, pos.Y - scaled / 2, scaled, scaled);
+    }
+
+    public static Rect ToHandleRect(Vector2d pos, double zoomboxScale)
+    {
+        double scaled = MoveHandleSize / zoomboxScale;
         return new Rect(pos.X - scaled / 2, pos.Y - scaled / 2, scaled, scaled);
     }
 
@@ -183,10 +197,6 @@ internal static class TransformHelper
         if (IsWithinAnchor((bottomLeft - bottomRight) / 2 + bottomRight, pos, zoomboxScale))
             return Anchor.Bottom;
 
-        // rotation
-        if (IsWithinAnchor(GetRotPos(corners, zoomboxScale), pos, zoomboxScale))
-            return Anchor.Rotation;
-
         // origin
         if (IsWithinAnchor(origin, pos, zoomboxScale))
             return Anchor.Origin;
@@ -195,12 +205,24 @@ internal static class TransformHelper
 
     public static bool IsWithinAnchor(Vector2d anchorPos, Vector2d mousePos, double zoomboxScale)
     {
-        return (anchorPos - mousePos).TaxicabLength <= (SideLength + 6) / zoomboxScale / 2;
+        var delta = (anchorPos - mousePos).Abs();
+        double scaled = AnchorSize / zoomboxScale / 2;
+        return delta.X < scaled && delta.Y < scaled;
+    }
+
+    public static bool IsWithinTransformHandle(Vector2d handlePos, Vector2d mousePos, double zoomboxScale)
+    {
+        var delta = (handlePos - mousePos).Abs();
+        double scaled = MoveHandleSize / zoomboxScale / 2;
+        return delta.X < scaled && delta.Y < scaled;
     }
 
     public static void DrawOverlay
-        (DrawingContext context, ShapeCorners corners, Vector2d origin, double zoomboxScale)
+        (DrawingContext context, Vector2d size, ShapeCorners corners, Vector2d origin, double zoomboxScale)
     {
+        // draw transparent background to enable mouse input everywhere
+        context.DrawRectangle(Brushes.Transparent, null, new Rect(new Point(0, 0), new Size(size.X, size.Y)));
+
         blackPen.Thickness = 1 / zoomboxScale;
         blackDashedPen.Thickness = 1 / zoomboxScale;
         whiteDashedPen.Thickness = 1 / zoomboxScale;
@@ -222,31 +244,40 @@ internal static class TransformHelper
         context.DrawLine(blackDashedPen, ToPoint(bottomRight), ToPoint(topRight));
         context.DrawLine(whiteDashedPen, ToPoint(bottomRight), ToPoint(topRight));
 
-        // corners
-        context.DrawRectangle(Brushes.White, blackPen, ToRect(topLeft, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, ToRect(topRight, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, ToRect(bottomLeft, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, ToRect(bottomRight, zoomboxScale));
-
-        // sides
-        context.DrawRectangle(Brushes.White, blackPen, ToRect((topLeft - topRight) / 2 + topRight, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, ToRect((topLeft - bottomLeft) / 2 + bottomLeft, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, ToRect((bottomLeft - bottomRight) / 2 + bottomRight, zoomboxScale));
-        context.DrawRectangle(Brushes.White, blackPen, ToRect((topRight - bottomRight) / 2 + bottomRight, zoomboxScale));
+        // corner anchors
+        context.DrawRectangle(Brushes.White, blackPen, ToAnchorRect(topLeft, zoomboxScale));
+        context.DrawRectangle(Brushes.White, blackPen, ToAnchorRect(topRight, zoomboxScale));
+        context.DrawRectangle(Brushes.White, blackPen, ToAnchorRect(bottomLeft, zoomboxScale));
+        context.DrawRectangle(Brushes.White, blackPen, ToAnchorRect(bottomRight, zoomboxScale));
 
-        // rotation
-        Vector2d rotPos = GetRotPos(corners, zoomboxScale);
-        double radius = SideLength / zoomboxScale / 2;
-        context.DrawEllipse(Brushes.White, blackPen, ToPoint(rotPos), radius, radius);
+        // side anchors
+        context.DrawRectangle(Brushes.White, blackPen, ToAnchorRect((topLeft - topRight) / 2 + topRight, zoomboxScale));
+        context.DrawRectangle(Brushes.White, blackPen, ToAnchorRect((topLeft - bottomLeft) / 2 + bottomLeft, zoomboxScale));
+        context.DrawRectangle(Brushes.White, blackPen, ToAnchorRect((bottomLeft - bottomRight) / 2 + bottomRight, zoomboxScale));
+        context.DrawRectangle(Brushes.White, blackPen, ToAnchorRect((topRight - bottomRight) / 2 + bottomRight, zoomboxScale));
 
         // origin
+        double radius = AnchorSize / zoomboxScale / 2;
         context.DrawEllipse(Brushes.Transparent, blackFreqDashedPen, ToPoint(origin), radius, radius);
         context.DrawEllipse(Brushes.Transparent, whiteFreqDashedPen, ToPoint(origin), radius, radius);
+
+        // move handle
+        Vector2d handlePos = GetDragHandlePos(corners, zoomboxScale);
+        const double CrossSize = MoveHandleSize - 1;
+        context.DrawRectangle(Brushes.White, blackPen, ToHandleRect(handlePos, zoomboxScale));
+        handleGeometry.Transform = new MatrixTransform(
+            0, CrossSize / zoomboxScale,
+            CrossSize / zoomboxScale, 0,
+            handlePos.X - CrossSize / (zoomboxScale * 2), handlePos.Y - CrossSize / (zoomboxScale * 2)
+            );
+        context.DrawGeometry(Brushes.Black, null, handleGeometry);
     }
 
-    public static Vector2d GetRotPos(ShapeCorners corners, double zoomboxScale)
+    public static Vector2d GetDragHandlePos(ShapeCorners corners, double zoomboxScale)
     {
-        return (corners.TopLeft + corners.TopRight) / 2 +
-            (corners.TopLeft.Lerp(corners.TopRight, 0.5) - corners.BottomLeft.Lerp(corners.BottomRight, 0.5)).Normalize() * 15 / zoomboxScale;
+        Vector2d 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)));
+        return max + new Vector2d(MoveHandleSize / zoomboxScale, MoveHandleSize / zoomboxScale);
     }
 }

+ 85 - 28
src/PixiEditorPrototype/CustomControls/TransformOverlay/TransformOverlay.cs

@@ -1,4 +1,5 @@
-using System.Windows;
+using System;
+using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Media;
@@ -66,39 +67,89 @@ internal class TransformOverlay : Control
         set => SetValue(OriginProperty, value);
     }
 
-    private Anchor? capturedAnchor;
+    private bool isMoving = false;
+    private Vector2d mousePosOnStartMove = new();
+    private Vector2d originOnStartMove = new();
+    private ShapeCorners cornersOnStartMove = new();
+
+    private bool isRotating = false;
+    private Vector2d mousePosOnStartRotate = new();
+    private ShapeCorners cornersOnStartRotate = new();
 
-    private bool originMoved = false;
-    private ShapeCorners mouseDownCorners;
-    private Vector2d mouseDownOriginPos;
+    private Anchor? capturedAnchor;
+    private bool originWasManuallyDragged = false;
+    private ShapeCorners cornersOnStartAnchorDrag;
+    private Vector2d mousePosOnStartAnchorDrag;
+    private Vector2d originOnStartAnchorDrag;
 
     protected override void OnRender(DrawingContext drawingContext)
     {
         base.OnRender(drawingContext);
-        TransformHelper.DrawOverlay(drawingContext, Corners, Origin, ZoomboxScale);
+        TransformHelper.DrawOverlay(drawingContext, new(ActualWidth, ActualHeight), Corners, Origin, ZoomboxScale);
     }
 
     protected override void OnMouseDown(MouseButtonEventArgs e)
     {
         base.OnMouseDown(e);
 
+        e.Handled = true;
         var pos = TransformHelper.ToVector2d(e.GetPosition(this));
         var anchor = TransformHelper.GetAnchorInPosition(pos, Corners, Origin, ZoomboxScale);
-        if (anchor is null)
-            return;
-        capturedAnchor = anchor;
-
-        mouseDownCorners = Corners;
-        mouseDownOriginPos = Origin;
-
-        e.Handled = true;
+        if (anchor is not null)
+        {
+            capturedAnchor = anchor;
+            cornersOnStartAnchorDrag = Corners;
+            originOnStartAnchorDrag = Origin;
+            mousePosOnStartAnchorDrag = pos;
+        }
+        else if (Corners.IsPointInside(pos) || TransformHelper.IsWithinTransformHandle(TransformHelper.GetDragHandlePos(Corners, ZoomboxScale), pos, ZoomboxScale))
+        {
+            isMoving = true;
+            mousePosOnStartMove = TransformHelper.ToVector2d(e.GetPosition(this));
+            originOnStartMove = Origin;
+            cornersOnStartMove = Corners;
+        }
+        else
+        {
+            isRotating = true;
+            mousePosOnStartRotate = TransformHelper.ToVector2d(e.GetPosition(this));
+            cornersOnStartRotate = Corners;
+        }
         CaptureMouse();
     }
 
     protected override void OnMouseMove(MouseEventArgs e)
     {
-        if (capturedAnchor is null)
+        if (capturedAnchor is not null)
+        {
+            HandleCapturedAnchorMovement(e);
             return;
+        }
+        else if (isMoving)
+        {
+            var pos = TransformHelper.ToVector2d(e.GetPosition(this));
+            var delta = pos - mousePosOnStartMove;
+            Origin = originOnStartMove + delta;
+            Corners = new ShapeCorners()
+            {
+                BottomLeft = cornersOnStartMove.BottomLeft + delta,
+                BottomRight = cornersOnStartMove.BottomRight + delta,
+                TopLeft = cornersOnStartMove.TopLeft + delta,
+                TopRight = cornersOnStartMove.TopRight + delta,
+            };
+        }
+        else if (isRotating)
+        {
+            var pos = TransformHelper.ToVector2d(e.GetPosition(this));
+            var angle = (mousePosOnStartRotate - Origin).CCWAngleTo(pos - Origin);
+            Corners = TransformUpdateHelper.UpdateShapeFromRotation(cornersOnStartRotate, Origin, angle);
+        }
+    }
+
+    private void HandleCapturedAnchorMovement(MouseEventArgs e)
+    {
+        if (capturedAnchor is null)
+            throw new InvalidOperationException("No anchor is captured");
         e.Handled = true;
         if (TransformHelper.IsCorner((Anchor)capturedAnchor) && CornerFreedom == TransformCornerFreedom.Locked ||
             TransformHelper.IsSide((Anchor)capturedAnchor) && SideFreedom == TransformSideFreedom.Locked)
@@ -108,30 +159,24 @@ internal class TransformOverlay : Control
 
         if (TransformHelper.IsCorner((Anchor)capturedAnchor))
         {
-            var newCorners = TransformUpdateHelper.UpdateShapeFromCorner((Anchor)capturedAnchor, CornerFreedom, mouseDownCorners, pos);
+            var newCorners = TransformUpdateHelper.UpdateShapeFromCorner((Anchor)capturedAnchor, CornerFreedom, cornersOnStartAnchorDrag, pos - mousePosOnStartAnchorDrag);
             if (newCorners is not null)
                 Corners = (ShapeCorners)newCorners;
-            if (!originMoved)
+            if (!originWasManuallyDragged)
                 Origin = TransformHelper.OriginFromCorners(Corners);
         }
         else if (TransformHelper.IsSide((Anchor)capturedAnchor))
         {
-            var newCorners = TransformUpdateHelper.UpdateShapeFromSide((Anchor)capturedAnchor, SideFreedom, mouseDownCorners, pos);
+            var newCorners = TransformUpdateHelper.UpdateShapeFromSide((Anchor)capturedAnchor, SideFreedom, cornersOnStartAnchorDrag, pos - mousePosOnStartAnchorDrag);
             if (newCorners is not null)
                 Corners = (ShapeCorners)newCorners;
-            if (!originMoved)
+            if (!originWasManuallyDragged)
                 Origin = TransformHelper.OriginFromCorners(Corners);
         }
-        else if (capturedAnchor == Anchor.Rotation)
-        {
-            var cur = TransformHelper.GetRotPos(mouseDownCorners, ZoomboxScale);
-            var angle = (cur - mouseDownOriginPos).CCWAngleTo(pos - mouseDownOriginPos);
-            Corners = TransformUpdateHelper.UpdateShapeFromRotation(mouseDownCorners, mouseDownOriginPos, angle);
-        }
         else if (capturedAnchor == Anchor.Origin)
         {
-            originMoved = true;
-            Origin = pos;
+            originWasManuallyDragged = true;
+            Origin = originOnStartAnchorDrag + pos - mousePosOnStartAnchorDrag;
         }
     }
 
@@ -140,12 +185,24 @@ internal class TransformOverlay : Control
         base.OnMouseUp(e);
         if (ReleaseAnchor())
             e.Handled = true;
+        else if (isMoving)
+        {
+            isMoving = false;
+            e.Handled = true;
+            ReleaseMouseCapture();
+        }
+        else if (isRotating)
+        {
+            isRotating = false;
+            e.Handled = true;
+            ReleaseMouseCapture();
+        }
     }
 
     private static void OnRequestedCorners(DependencyObject obj, DependencyPropertyChangedEventArgs args)
     {
         TransformOverlay overlay = (TransformOverlay)obj;
-        overlay.originMoved = false;
+        overlay.originWasManuallyDragged = false;
         overlay.Corners = (ShapeCorners)args.NewValue;
         overlay.Origin = TransformHelper.OriginFromCorners(overlay.Corners);
     }

+ 7 - 2
src/PixiEditorPrototype/CustomControls/TransformOverlay/TransformUpdateHelper.cs

@@ -5,7 +5,7 @@ namespace PixiEditorPrototype.CustomControls.TransformOverlay;
 internal static class TransformUpdateHelper
 {
     public static ShapeCorners? UpdateShapeFromCorner
-        (Anchor targetCorner, TransformCornerFreedom freedom, ShapeCorners corners, Vector2d desiredPos)
+        (Anchor targetCorner, TransformCornerFreedom freedom, ShapeCorners corners, Vector2d movement)
     {
         if (!TransformHelper.IsCorner(targetCorner))
             throw new ArgumentException($"{targetCorner} is not a corner");
@@ -16,6 +16,7 @@ internal static class TransformUpdateHelper
         if (freedom is TransformCornerFreedom.ScaleProportionally or TransformCornerFreedom.Scale)
         {
             var targetPos = TransformHelper.GetAnchorPosition(corners, targetCorner);
+            Vector2d desiredPos = targetPos + movement;
             var opposite = TransformHelper.GetOpposite(targetCorner);
             var oppositePos = TransformHelper.GetAnchorPosition(corners, opposite);
 
@@ -45,6 +46,8 @@ internal static class TransformUpdateHelper
 
         if (freedom == TransformCornerFreedom.Free)
         {
+            var targetPos = TransformHelper.GetAnchorPosition(corners, targetCorner);
+            Vector2d desiredPos = targetPos + movement;
             var newCorners = TransformHelper.UpdateCorner(corners, targetCorner, desiredPos);
             return newCorners.IsLegal ? newCorners : null;
         }
@@ -52,7 +55,7 @@ internal static class TransformUpdateHelper
     }
 
     public static ShapeCorners? UpdateShapeFromSide
-        (Anchor targetSide, TransformSideFreedom freedom, ShapeCorners corners, Vector2d desiredPos)
+        (Anchor targetSide, TransformSideFreedom freedom, ShapeCorners corners, Vector2d movement)
     {
         if (!TransformHelper.IsSide(targetSide))
             throw new ArgumentException($"{targetSide} is not a side");
@@ -63,6 +66,7 @@ internal static class TransformUpdateHelper
         if (freedom is TransformSideFreedom.ScaleProportionally)
         {
             var targetPos = TransformHelper.GetAnchorPosition(corners, targetSide);
+            Vector2d desiredPos = targetPos + movement;
             var opposite = TransformHelper.GetOpposite(targetSide);
             var oppositePos = TransformHelper.GetAnchorPosition(corners, opposite);
 
@@ -96,6 +100,7 @@ internal static class TransformUpdateHelper
             var side1Pos = TransformHelper.GetAnchorPosition(corners, side1);
             var side2Pos = TransformHelper.GetAnchorPosition(corners, side2);
             var targetPos = TransformHelper.GetAnchorPosition(corners, targetSide);
+            var desiredPos = targetPos + movement;
 
             var opposite = TransformHelper.GetOpposite(targetSide);
             var oppPos = TransformHelper.GetAnchorPosition(corners, opposite);