Browse Source

Improve transform overlay

Equbuxu 3 years ago
parent
commit
e341e504a4

+ 1 - 0
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -18,6 +18,7 @@ public record struct ShapeData
     public SKColor FillColor { get; }
     public SKBlendMode BlendMode { get; }
     public VecD Center { get; }
+
     /// <summary>Can be negative to show flipping </summary>
     public VecD Size { get; }
     public double Angle { get; }

+ 11 - 2
src/PixiEditor/Models/DocumentModels/Public/DocumentEventsModule.cs

@@ -1,5 +1,6 @@
 using System.Windows.Input;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.Models.Events;
 using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.Models.DocumentModels.Public;
@@ -17,8 +18,16 @@ internal class DocumentEventsModule
     public void OnKeyDown(Key args) { }
     public void OnKeyUp(Key args) { }
 
-    public void OnConvertedKeyDown(Key args) => Internals.ChangeController.ConvertedKeyDownInlet(args);
-    public void OnConvertedKeyUp(Key args) => Internals.ChangeController.ConvertedKeyUpInlet(args);
+    public void OnConvertedKeyDown(FilteredKeyEventArgs args)
+    {
+        Internals.ChangeController.ConvertedKeyDownInlet(args.Key);
+        Document.TransformViewModel.ModifierKeysInlet(args.IsShiftDown, args.IsCtrlDown, args.IsAltDown);
+    }
+    public void OnConvertedKeyUp(FilteredKeyEventArgs args)
+    {
+        Internals.ChangeController.ConvertedKeyUpInlet(args.Key);
+        Document.TransformViewModel.ModifierKeysInlet(args.IsShiftDown, args.IsCtrlDown, args.IsAltDown);
+    }
 
     public void OnCanvasLeftMouseButtonDown(VecD pos) => Internals.ChangeController.LeftMouseButtonDownInlet(pos);
     public void OnCanvasMouseMove(VecD newPos)

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

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Actions;
+using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
@@ -19,6 +20,7 @@ internal class EllipseToolExecutor : ShapeToolExecutor<EllipseToolViewModel>
         internals!.ActionAccumulator.AddActions(new DrawEllipse_Action(memberGuid, rect, strokeColor, fillColor, strokeWidth, drawOnMask));
     }
 
+    protected override DocumentTransformMode TransformMode => DocumentTransformMode.NoRotation;
     protected override void DrawShape(VecI currentPos) => DrawEllipseOrCircle(currentPos);
 
     protected override IAction TransformMovedAction(ShapeData data, ShapeCorners corners) =>

+ 3 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ShapeToolExecutor.cs

@@ -50,6 +50,7 @@ internal abstract class ShapeToolExecutor<T> : UpdateableChangeExecutor where T
     protected abstract void DrawShape(VecI currentPos);
     protected abstract IAction TransformMovedAction(ShapeData data, ShapeCorners corners);
     protected abstract IAction EndDrawAction();
+    protected virtual DocumentTransformMode TransformMode => DocumentTransformMode.Rotation;
 
     protected RectI GetSquaredCoordinates(VecI startPos, VecI curPos)
     {
@@ -66,7 +67,7 @@ internal abstract class ShapeToolExecutor<T> : UpdateableChangeExecutor where T
         if (!transforming)
             return;
 
-        var rect = (RectI)RectD.FromCenterAndSize(corners.RectCenter, corners.RectSize);
+        var rect = RectD.FromCenterAndSize(corners.RectCenter, corners.RectSize);
         ShapeData shapeData = new ShapeData(rect.Center, rect.Size, corners.RectRotation, strokeWidth, strokeColor,
             fillColor);
         IAction drawAction = TransformMovedAction(shapeData, corners);
@@ -94,7 +95,7 @@ internal abstract class ShapeToolExecutor<T> : UpdateableChangeExecutor where T
         if (transforming)
             return;
         transforming = true;
-        document!.TransformViewModel.ShowRotatingShapeTransform(new ShapeCorners(lastRect));
+        document!.TransformViewModel.ShowTransform(TransformMode, new ShapeCorners(lastRect));
     }
 
     public override void ForceStop()

+ 7 - 0
src/PixiEditor/Models/Enums/DocumentTransformMode.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.Models.Enums;
+internal enum DocumentTransformMode
+{
+    NoRotation,
+    Rotation,
+    Freeform
+}

+ 35 - 17
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentTransformViewModel.cs

@@ -1,5 +1,6 @@
 using System.ComponentModel;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.Models.Enums;
 using PixiEditor.Views.UserControls.TransformOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Document;
@@ -86,35 +87,52 @@ internal class DocumentTransformViewModel : INotifyPropertyChanged
     public event PropertyChangedEventHandler? PropertyChanged;
     public event EventHandler<ShapeCorners>? TransformMoved;
 
+    private DocumentTransformMode activeTransformMode = DocumentTransformMode.Rotation;
+
     public void HideTransform()
     {
         TransformActive = false;
     }
 
-    public void ShowFixedAngleShapeTransform(ShapeCorners initPos)
+    public void ShowTransform(DocumentTransformMode mode, ShapeCorners initPos)
     {
+        activeTransformMode = mode;
         CornerFreedom = TransformCornerFreedom.Scale;
-        SideFreedom = TransformSideFreedom.ScaleProportionally;
-        LockRotation = true;
+        SideFreedom = TransformSideFreedom.Stretch;
+        LockRotation = mode == DocumentTransformMode.NoRotation;
         RequestedCorners = initPos;
         TransformActive = true;
     }
 
-    public void ShowRotatingShapeTransform(ShapeCorners initPos)
+    public void ModifierKeysInlet(bool isShiftDown, bool isCtrlDown, bool isAltDown)
     {
-        CornerFreedom = TransformCornerFreedom.Scale;
-        SideFreedom = TransformSideFreedom.ScaleProportionally;
-        RequestedCorners = initPos;
-        LockRotation = false;
-        TransformActive = true;
-    }
+        var requestedCornerFreedom = TransformCornerFreedom.Scale;
+        var requestedSideFreedom = TransformSideFreedom.Stretch;
 
-    public void ShowFreeTransform(ShapeCorners initPos)
-    {
-        CornerFreedom = TransformCornerFreedom.Free;
-        SideFreedom = TransformSideFreedom.Free;
-        RequestedCorners = initPos;
-        LockRotation = false;
-        TransformActive = true;
+        if (isShiftDown)
+        {
+            requestedCornerFreedom = TransformCornerFreedom.ScaleProportionally;
+            requestedSideFreedom = TransformSideFreedom.ScaleProportionally;
+        }
+        else if (isCtrlDown)
+        {
+            requestedCornerFreedom = TransformCornerFreedom.Free;
+            requestedSideFreedom = TransformSideFreedom.Free;
+        }
+        else if (isAltDown)
+        {
+            requestedSideFreedom = TransformSideFreedom.Shear;
+        }
+        else
+        {
+            requestedCornerFreedom = TransformCornerFreedom.Scale;
+            requestedSideFreedom = TransformSideFreedom.Stretch;
+        }
+
+        if (requestedCornerFreedom != TransformCornerFreedom.Free || activeTransformMode == DocumentTransformMode.Freeform)
+            CornerFreedom = requestedCornerFreedom;
+        if (requestedSideFreedom is not (TransformSideFreedom.Free or TransformSideFreedom.Shear) ||
+            activeTransformMode == DocumentTransformMode.Freeform)
+            SideFreedom = requestedSideFreedom;
     }
 }

+ 2 - 2
src/PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -52,13 +52,13 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
 
     private void OnConvertedKeyDown(object? sender, FilteredKeyEventArgs args)
     {
-        Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnConvertedKeyDown(args.Key);
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnConvertedKeyDown(args);
         Owner.ToolsSubViewModel.ConvertedKeyDownInlet(args);
     }
 
     private void OnConvertedKeyUp(object? sender, FilteredKeyEventArgs args)
     {
-        Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnConvertedKeyUp(args.Key);
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnConvertedKeyUp(args);
         Owner.ToolsSubViewModel.ConvertedKeyUpInlet(args);
     }
 

+ 35 - 0
src/PixiEditor/Views/UserControls/TransformOverlay/TransformHelper.cs

@@ -1,4 +1,5 @@
 using System.Windows;
+using System.Windows.Input;
 using ChunkyImageLib.DataHolders;
 #nullable enable
 
@@ -32,6 +33,40 @@ internal static class TransformHelper
         return corners;
     }
 
+    public static Cursor GetResizeCursor(Anchor anchor, ShapeCorners corners)
+    {
+        double angle;
+        if (IsSide(anchor))
+        {
+            var (left, right) = GetCornersOnSide(anchor);
+            VecD leftPos = GetAnchorPosition(corners, left);
+            VecD rightPos = GetAnchorPosition(corners, right);
+            angle = (leftPos - rightPos).Angle + Math.PI / 2;
+        }
+        else if (IsCorner(anchor))
+        {
+            var (left, right) = GetNeighboringCorners(anchor);
+            VecD leftPos = GetAnchorPosition(corners, left);
+            VecD curPos = GetAnchorPosition(corners, anchor);
+            VecD rightPos = GetAnchorPosition(corners, right);
+            angle = ((curPos - leftPos).Normalize() + (curPos - rightPos).Normalize()).Angle;
+        }
+        else
+        {
+            return Cursors.Arrow;
+        }
+        angle = Math.Round(angle * 4 / Math.PI);
+        angle = (int)((angle % 8 + 8) % 8);
+        if (angle is (0 or 4))
+            return Cursors.SizeWE;
+        else if (angle is (2 or 6))
+            return Cursors.SizeNS;
+        else if (angle is (1 or 5))
+            return Cursors.SizeNWSE;
+        else
+            return Cursors.SizeNESW;
+    }
+
     private static double GetSnappingAngle(double angle)
     {
         return Math.Round(angle * 8 / (Math.PI * 2)) * (Math.PI * 2) / 8;

+ 30 - 7
src/PixiEditor/Views/UserControls/TransformOverlay/TransformOverlay.cs

@@ -29,7 +29,8 @@ internal class TransformOverlay : Decorator
             new PropertyMetadata(TransformCornerFreedom.Locked));
 
     public static readonly DependencyProperty LockRotationProperty =
-        DependencyProperty.Register(nameof(LockRotation), typeof(bool), typeof(TransformOverlay), new(false));
+        DependencyProperty.Register(nameof(LockRotation), typeof(bool), typeof(TransformOverlay),
+            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
 
     public static readonly DependencyProperty SnapToAnglesProperty =
         DependencyProperty.Register(nameof(SnapToAngles), typeof(bool), typeof(TransformOverlay), new PropertyMetadata(false));
@@ -127,7 +128,9 @@ internal class TransformOverlay : Decorator
     {
         base.OnRender(drawingContext);
         DrawOverlay(drawingContext, new(ActualWidth, ActualHeight), Corners, InternalState.Origin, ZoomboxScale);
-        UpdateRotationCursor(TransformHelper.ToVecD(Mouse.GetPosition(this)));
+
+        if (capturedAnchor is null)
+            UpdateRotationCursor(TransformHelper.ToVecD(Mouse.GetPosition(this)));
     }
 
     private void DrawOverlay
@@ -235,11 +238,12 @@ internal class TransformOverlay : Decorator
         return TransformHelper.GetAnchorInPosition(mousePos, Corners, InternalState.Origin, ZoomboxScale, 15) is not null;
     }
 
-    private void UpdateRotationCursor(VecD mousePos)
+    private bool UpdateRotationCursor(VecD mousePos)
     {
-        if (!ShouldRotate(mousePos))
+        if ((!ShouldRotate(mousePos) && !isRotating) || LockRotation)
         {
             rotateCursorGeometry.Transform = new ScaleTransform(0, 0);
+            return false;
         }
         else
         {
@@ -247,12 +251,13 @@ internal class TransformOverlay : Decorator
             matrix.RotateAt((mousePos - InternalState.Origin).Angle * 180 / Math.PI - 90, mousePos.X, mousePos.Y);
             matrix.ScaleAt(8 / ZoomboxScale, 8 / ZoomboxScale, mousePos.X, mousePos.Y);
             rotateCursorGeometry.Transform = new MatrixTransform(matrix);
+            return true;
         }
     }
 
     protected override void OnMouseMove(MouseEventArgs e)
     {
-        UpdateRotationCursor(TransformHelper.ToVecD(e.GetPosition(this)));
+        Cursor finalCursor = Cursors.Arrow;
 
         if (capturedAnchor is not null)
         {
@@ -260,9 +265,15 @@ internal class TransformOverlay : Decorator
             return;
         }
 
+        if (UpdateRotationCursor(TransformHelper.ToVecD(e.GetPosition(this))))
+            finalCursor = Cursors.None;
+
+        VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
+        Anchor? anchor = TransformHelper.GetAnchorInPosition(pos, Corners, InternalState.Origin, ZoomboxScale);
+
         if (isMoving)
         {
-            VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
+            finalCursor = Cursors.SizeAll;
             VecD delta = pos - mousePosOnStartMove;
 
             if (Corners.IsSnappedToPixels)
@@ -280,13 +291,20 @@ internal class TransformOverlay : Decorator
         }
         else if (isRotating)
         {
-            VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
+            finalCursor = Cursors.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);
         }
+        else if (anchor is not null)
+        {
+            finalCursor = TransformHelper.GetResizeCursor((Anchor)anchor, Corners);
+        }
+
+        if (Cursor != finalCursor)
+            Cursor = finalCursor;
     }
 
     private void HandleCapturedAnchorMovement(MouseEventArgs e)
@@ -338,7 +356,9 @@ internal class TransformOverlay : Decorator
         if (e.ChangedButton != MouseButton.Left)
             return;
         if (ReleaseAnchor())
+        {
             e.Handled = true;
+        }
         else if (isMoving)
         {
             isMoving = false;
@@ -350,6 +370,9 @@ internal class TransformOverlay : Decorator
             isRotating = false;
             e.Handled = true;
             ReleaseMouseCapture();
+            Cursor = Cursors.Arrow;
+            var pos = TransformHelper.ToVecD(e.GetPosition(this));
+            UpdateRotationCursor(pos);
         }
     }