Browse Source

A lot of vector improvements

flabbet 7 months ago
parent
commit
f36c1c0a08

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit d3d792644bb31ed627e57175e6f06a5a15dd09f6
+Subproject commit 5e6f8e7762ea56e299bf6ab1a880996a7b856fef

+ 12 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -17,6 +17,7 @@ using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Tools;
 using PixiEditor.ViewModels.Tools.Tools;
+using PixiEditor.Views.Overlays.PathOverlay;
 using Color = Drawie.Backend.Core.ColorsImpl.Color;
 using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
 
@@ -136,7 +137,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
 
     public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
     {
-        if (!isValidPathLayer || startingPath.IsClosed) 
+        bool allClosed = WholePathClosed();
+        if (!isValidPathLayer || allClosed)
         {
             if (NeedsNewLayer(document.SelectedStructureMember, document.AnimationHandler.ActiveFrameTime))
             {
@@ -151,7 +153,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
             return;
         }
 
-        if (args.KeyModifiers == KeyModifiers.None)
+        /*if (args.KeyModifiers == KeyModifiers.None)
         {
 
             VecD mouseSnap =
@@ -171,7 +173,14 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
 
             internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(member.Id, vectorData));
             mouseDown = true;
-        }
+        }*/
+    }
+    
+    private bool WholePathClosed()
+    {
+        EditableVectorPath editablePath = new EditableVectorPath(startingPath);
+        
+        return editablePath.SubShapes.Count > 0 && editablePath.SubShapes.All(x => x.IsClosed);
     }
 
     public override void OnLeftMouseButtonUp(VecD pos)

+ 28 - 11
src/PixiEditor/Views/Overlays/Overlay.cs

@@ -50,7 +50,7 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
     public event PointerEvent? PointerMovedOverlay;
     public event PointerEvent? PointerPressedOverlay;
     public event PointerEvent? PointerReleasedOverlay;
-    
+
     public Handle? CapturedHandle { get; set; } = null!;
 
     private readonly Dictionary<AvaloniaProperty, OverlayTransition> activeTransitions = new();
@@ -87,42 +87,59 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
     {
         CapturedHandle = handle;
     }
-    
+
     public void EnterPointer(OverlayPointerArgs args)
     {
         OnOverlayPointerEntered(args);
+        if (args.Handled) return;
         InvokeHandleEvent(HandleEventType.PointerEnteredOverlay, args);
+        if (args.Handled) return;
         PointerEnteredOverlay?.Invoke(args);
     }
 
     public void ExitPointer(OverlayPointerArgs args)
     {
         OnOverlayPointerExited(args);
+        if (args.Handled) return;
         InvokeHandleEvent(HandleEventType.PointerExitedOverlay, args);
+        if (args.Handled) return;
         PointerExitedOverlay?.Invoke(args);
     }
 
     public void MovePointer(OverlayPointerArgs args)
     {
-        OnOverlayPointerMoved(args);
         InvokeHandleEvent(HandleEventType.PointerMovedOverlay, args);
+        if (args.Handled) return;
+        OnOverlayPointerMoved(args);
+        if (args.Handled) return;
         PointerMovedOverlay?.Invoke(args);
     }
 
     public void PressPointer(OverlayPointerArgs args)
     {
-        OnOverlayPointerPressed(args);
         InvokeHandleEvent(HandleEventType.PointerPressedOverlay, args);
+        if (args.Handled) return;
+        OnOverlayPointerPressed(args);
+        if (args.Handled) return;
         PointerPressedOverlay?.Invoke(args);
     }
 
     public void ReleasePointer(OverlayPointerArgs args)
     {
-        OnOverlayPointerReleased(args);
         InvokeHandleEvent(HandleEventType.PointerReleasedOverlay, args);
+        if (args.Handled)
+        {
+            CaptureHandle(null);
+            return;
+        }
+        OnOverlayPointerReleased(args);
+        if (args.Handled)
+        {
+            CaptureHandle(null);
+            return;
+        }
+
         PointerReleasedOverlay?.Invoke(args);
-        
-        CaptureHandle(null);
     }
 
     public virtual bool TestHit(VecD point)
@@ -156,7 +173,7 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
             }
         }
     }
-    
+
     private void InvokeHandleEvent(HandleEventType eventName, OverlayPointerArgs args)
     {
         if (CapturedHandle != null)
@@ -172,11 +189,11 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
             }
         }
     }
-    
+
     private void InvokeHandleEvent(Handle handle, OverlayPointerArgs args, HandleEventType pointerEvent)
     {
-        if(pointerEvent == null) return;
-        
+        if (pointerEvent == null) return;
+
         if (pointerEvent == HandleEventType.PointerMovedOverlay)
         {
             handle.InvokeMove(args);

+ 12 - 7
src/PixiEditor/Views/Overlays/PathOverlay/EditableVectorPath.cs

@@ -117,8 +117,6 @@ public class EditableVectorPath
                 subShapes.Add(new SubShape(currentSubShapePoints, isSubShapeClosed));
 
                 currentSubShapePoints.Clear();
-
-                currentSubShapeStartingIndex = globalVerbIndex;
             }
             else
             {
@@ -129,6 +127,12 @@ public class EditableVectorPath
                     {
                         subShapes.Add(new SubShape(currentSubShapePoints, isSubShapeClosed));
                         currentSubShapePoints.Clear();
+                        
+                        currentSubShapePoints.Add(new ShapePoint(data.points[0], 0, new Verb()));
+                    }
+                    else
+                    {
+                        currentSubShapePoints.Add(new ShapePoint(data.points[0], 0, new Verb()));
                     }
                 }
                 else
@@ -264,14 +268,15 @@ public class EditableVectorPath
     public VecD? GetClosestPointOnPath(VecD point, float maxDistanceInPixels)
     {
         VecD? closest = null;
-        
+
         foreach (var subShape in subShapes)
         {
             VecD? closestInSubShape = subShape.GetClosestPointOnPath(point, maxDistanceInPixels);
-            
+
             if (closestInSubShape != null)
             {
-                if (closest == null || VecD.Distance(closestInSubShape.Value, point) < VecD.Distance(closest.Value, point))
+                if (closest == null ||
+                    VecD.Distance(closestInSubShape.Value, point) < VecD.Distance(closest.Value, point))
                 {
                     closest = closestInSubShape;
                 }
@@ -294,7 +299,7 @@ public class EditableVectorPath
                 break;
             }
         }
-        
-        targetSubShape?.AddPointAt((VecF)point, verb);
+
+        targetSubShape?.InsertPointAt((VecF)point, verb);
     }
 }

+ 39 - 2
src/PixiEditor/Views/Overlays/PathOverlay/SubShape.cs

@@ -10,7 +10,7 @@ public class SubShape
     private List<ShapePoint> points;
 
     public IReadOnlyList<ShapePoint> Points => points;
-    public bool IsClosed { get; }
+    public bool IsClosed { get; private set; }
 
     public ShapePoint? GetNextPoint(int nextToIndex)
     {
@@ -69,8 +69,27 @@ public class SubShape
             }
         }
     }
+    
+    public void AppendPoint(VecF point)
+    {
+        if (points.Count == 0)
+        {
+            VecF[] data = new VecF[4];
+            data[0] = VecF.Zero;
+            data[1] = point;
+            points.Add(new ShapePoint(point, 0, new Verb((PathVerb.Move, data, 0))));
+        }
+        else
+        {
+            var lastPoint = points[^1];
+            VecF[] data = new VecF[4];
+            data[0] = lastPoint.Position;
+            data[1] = point;
+            points.Add(new ShapePoint(point, lastPoint.Index + 1, new Verb((PathVerb.Line, data, 0))));
+        }
+    }
 
-    public void AddPointAt(VecF point, Verb pointVerb)
+    public void InsertPointAt(VecF point, Verb pointVerb)
     {
         int indexOfVerb = this.points.FirstOrDefault(x => x.Verb == pointVerb)?.Index ?? -1;
         if (indexOfVerb == -1)
@@ -159,4 +178,22 @@ public class SubShape
 
         return null;
     }
+
+    public void Close()
+    {
+        if (IsClosed)
+        {
+            return;
+        }
+
+        IsClosed = true;
+        
+        if (points.Count > 1)
+        {
+            VecF[] data = new VecF[4];
+            data[0] = points[^1].Position;
+            data[1] = points[0].Position;
+            points.Add(new ShapePoint(points[0].Position, points[^1].Index + 1, new Verb((PathVerb.Line, data, 0))));
+        }
+    }
 }

+ 1 - 9
src/PixiEditor/Views/Overlays/PathOverlay/VectorMath.cs

@@ -92,15 +92,7 @@ internal static class VectorMath
 
     public static bool IsPointOnLine(VecD point, VecD start, VecD end)
     {
-        VecD startToPoint = point - start;
-        VecD startToEnd = end - start;
-
-        double sqrtMagnitudeToEnd = Math.Pow(startToEnd.X, 2) + Math.Pow(startToEnd.Y, 2);
-
-        double dot = startToPoint.X * startToEnd.X + startToPoint.Y * startToEnd.Y;
-        var t = dot / sqrtMagnitudeToEnd;
-
-        return t is >= 0 and <= 1;
+        return Math.Abs(VecD.Distance(start, point) + VecD.Distance(end, point) - VecD.Distance(start, end)) < 0.001f;
     }
 
     public static VecD GetClosestPointOnQuad(VecD point, VecD start, VecD controlPoint, VecD end)

+ 76 - 13
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -76,6 +76,7 @@ public class VectorPathOverlay : Overlay
         AddHandle(transformHandle);
 
         insertPreviewHandle = new AnchorHandle(this);
+        insertPreviewHandle.HitTestVisible = false;
 
         AddHandle(insertPreviewHandle);
     }
@@ -134,8 +135,12 @@ public class VectorPathOverlay : Overlay
 
             foreach (var point in subPath.Points)
             {
+                if (anchorIndex >= anchorHandles.Count)
+                {
+                    break;
+                }
+                
                 var handle = anchorHandles[anchorIndex];
-                //handle.Position = (VecD)point.Position;
 
                 if (point.Verb.ControlPoint1 != null || point.Verb.ControlPoint2 != null)
                 {
@@ -186,6 +191,11 @@ public class VectorPathOverlay : Overlay
         }
     }
 
+    public override bool TestHit(VecD point)
+    {
+        return Path != null;
+    }
+
     private void AdjustHandles(EditableVectorPath path)
     {
         int pointsCount = path.TotalPoints + path.ControlPointsCount;
@@ -210,7 +220,6 @@ public class VectorPathOverlay : Overlay
                 CreateHandle(controlPointHandles.Count, true);
             }
 
-
             SelectAnchor(GetHandleAt(pointsCount - 1));
 
             ConnectControlPointsToAnchors();
@@ -239,6 +248,8 @@ public class VectorPathOverlay : Overlay
                     controlPointIndex += 2;
                 }
 
+                if (anchorIndex >= anchorHandles.Count) continue;
+
                 var anchor = anchorHandles[anchorIndex];
                 anchor.Position = (VecD)point.Position;
                 anchorIndex++;
@@ -383,30 +394,46 @@ public class VectorPathOverlay : Overlay
             return;
         }
 
-        if (Path.IsClosed)
+        if (args.Modifiers.HasFlag(KeyModifiers.Control))
         {
+            SelectAnchor(anchorHandle);
             return;
         }
 
-        VectorPath newPath = new VectorPath(Path);
-        if (args.Modifiers.HasFlag(KeyModifiers.Control))
+        var selectedHandle = anchorHandles.FirstOrDefault(h => h.IsSelected);
+        if (selectedHandle == null)
         {
-            SelectAnchor(anchorHandle);
             return;
         }
 
-        if (anchorHandles.IndexOf(anchorHandle) == 0)
+        SubShape ssOfSelected = editableVectorPath.GetSubShapeContainingIndex(anchorHandles.IndexOf(selectedHandle));
+        SubShape ssOfTapped = editableVectorPath.GetSubShapeContainingIndex(anchorHandles.IndexOf(anchorHandle));
+        
+        if(ssOfTapped == null || ssOfSelected == null)
         {
-            newPath.LineTo((VecF)anchorHandle.Position);
-            newPath.Close();
+            return;
+        }
+
+        int globalIndexOfTapped = anchorHandles.IndexOf(anchorHandle);
+        int localIndexOfTapped = editableVectorPath.GetSubShapePointIndex(globalIndexOfTapped, ssOfTapped);
+
+        if (ssOfSelected == ssOfTapped && ssOfTapped.IsClosed)
+        {
+            return;
+        }
+        
+        if (ssOfSelected == ssOfTapped && !ssOfTapped.IsClosed &&
+            (localIndexOfTapped == 0 || localIndexOfTapped == ssOfTapped.Points.Count - 1))
+        {
+            ssOfTapped.Close();
         }
         else
         {
-            VecD pos = anchorHandle.Position;
-            newPath.LineTo(new VecF((float)pos.X, (float)pos.Y));
+            ssOfTapped.AppendPoint((VecF)anchorHandle.Position);
         }
 
-        Path = newPath;
+        SelectAnchor(anchorHandle);
+        Path = editableVectorPath.ToVectorPath();
     }
 
     private void SelectAnchor(AnchorHandle handle)
@@ -419,12 +446,21 @@ public class VectorPathOverlay : Overlay
 
     protected override void OnOverlayPointerPressed(OverlayPointerArgs args)
     {
+        if(args.PointerButton != MouseButton.Left)
+        {
+            return;
+        }
+        
         if (args.Modifiers.HasFlag(KeyModifiers.Shift) && IsOverPath(args.Point, out VecD closestPoint))
         {
             AddPointAt(closestPoint);
             AddToUndoCommand.Execute(Path);
             args.Handled = true;
         }
+        else if (args.Modifiers == KeyModifiers.None)
+        {
+            AddNewPointFromClick(args.Point);
+        }
     }
 
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
@@ -440,6 +476,30 @@ public class VectorPathOverlay : Overlay
         }
     }
 
+    private void AddNewPointFromClick(VecD point)
+    {
+        var selectedHandle = anchorHandles.FirstOrDefault(h => h.IsSelected);
+        SubShape subShape = editableVectorPath.GetSubShapeContainingIndex(anchorHandles.IndexOf(selectedHandle));
+
+        if (subShape.IsClosed)
+        {
+            return;
+        }
+
+        if (Path.IsEmpty)
+        {
+            Path = new VectorPath();
+            Path.LineTo((VecF)point);
+            SelectAnchor(anchorHandles[0]);
+        }
+        else
+        {
+            subShape.AppendPoint((VecF)point);
+            Path = editableVectorPath.ToVectorPath();
+            SelectAnchor(anchorHandles.Last());
+        }
+    }
+
     private void AddPointAt(VecD point)
     {
         editableVectorPath.AddPointAt(point);
@@ -459,8 +519,10 @@ public class VectorPathOverlay : Overlay
         if (source is AnchorHandle anchorHandle)
         {
             SnappingController.RemoveAll($"editingPath[{anchorHandles.IndexOf(anchorHandle)}]");
+            SelectAnchor(anchorHandle);
             CaptureHandle(source);
-
+            args.Handled = true;
+            
             if (!args.Modifiers.HasFlag(KeyModifiers.Control)) return;
 
             var newPath = ConvertTouchingVerbsToCubic(anchorHandle);
@@ -472,6 +534,7 @@ public class VectorPathOverlay : Overlay
             HandleContinousCubicDrag(anchorHandle.Position, anchorHandle, subShapeContainingIndex, localIndex, true);
 
             Path = newPath.ToVectorPath();
+
         }
     }
 

+ 32 - 0
tests/PixiEditor.Tests/EditableVectorPathTests.cs

@@ -361,6 +361,38 @@ public class EditableVectorPathTests
         
         Assert.True(editablePath.SubShapes[1].IsClosed);
     }
+    
+    [Fact]
+    public void TestThatMoveToProducesEmptyVerb()
+    {
+        VectorPath path = new VectorPath();
+        path.MoveTo(new VecF(0, 0));
+        
+        EditableVectorPath editablePath = new EditableVectorPath(path);
+        
+        Assert.Single(editablePath.SubShapes);
+        Assert.Single(editablePath.SubShapes[0].Points);
+        
+        Assert.Equal(null, editablePath.SubShapes[0].Points[0].Verb.VerbType);
+    }
+    
+    [Fact]
+    public void TestThatMultipleMoveToProduceEmptyVerbs()
+    {
+        VectorPath path = new VectorPath();
+        path.MoveTo(new VecF(0, 0));
+        path.MoveTo(new VecF(2, 2));
+        
+        EditableVectorPath editablePath = new EditableVectorPath(path);
+        
+        Assert.Equal(2, editablePath.SubShapes.Count);
+        
+        Assert.Single(editablePath.SubShapes[0].Points);
+        Assert.Single(editablePath.SubShapes[1].Points);
+        
+        Assert.Null(editablePath.SubShapes[0].Points[0].Verb.VerbType);
+        Assert.Null(editablePath.SubShapes[1].Points[0].Verb.VerbType);
+    }
 
     [Fact]
     public void TestThatEditingPointResultsInCorrectVectorPath()