瀏覽代碼

Some path manipulation

flabbet 9 月之前
父節點
當前提交
6b79e1fe3f

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 29fd7c99a39b8b24b8c9f601856fe839febbb4e7
+Subproject commit a98c9d433dd46e0c8462e407d76c22fe39305f02

+ 2 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -763,5 +763,6 @@
   "TOGGLE_SNAPPING": "Toggle snapping",
   "HIGH_RES_PREVIEW": "High Resolution Preview",
   "LOW_RES_PREVIEW": "Document Resolution Preview",
-  "TOGGLE_HIGH_RES_PREVIEW": "Toggle high resolution preview"
+  "TOGGLE_HIGH_RES_PREVIEW": "Toggle high resolution preview",
+  "FACTOR": "Factor"
 }

+ 9 - 0
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -3,6 +3,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Enums;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
@@ -280,4 +281,12 @@ internal class ChangeExecutionController
         
         return default;
     }
+
+    public void PathOverlayChangedInlet(VectorPath path)
+    {
+        if (currentSession is IPathExecutor vectorPathToolExecutor)
+        {
+            vectorPathToolExecutor.OnPathChanged(path);
+        }
+    }
 }

+ 8 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/IPathExecutor.cs

@@ -0,0 +1,8 @@
+using Drawie.Backend.Core.Vector;
+
+namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
+
+public interface IPathExecutor
+{
+    public void OnPathChanged(VectorPath path);
+}

+ 8 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -7,6 +7,7 @@ using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Controllers.InputDevice;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Tools;
 using Color = Drawie.Backend.Core.ColorsImpl.Color;
@@ -14,7 +15,7 @@ using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
-internal class VectorPathToolExecutor : UpdateableChangeExecutor
+internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor
 {
     private IStructureMemberHandler member;
     private VectorPath startingPath;
@@ -96,4 +97,10 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor
             FillColor = toolbar.Fill ? toolbar.FillColor.ToColor() : Colors.Transparent,
         };
     }
+
+    public void OnPathChanged(VectorPath path)
+    {
+        startingPath = path;
+        internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(member.Id, ConstructShapeData()));
+    }
 }

+ 1 - 0
src/PixiEditor/Models/Handlers/IPathOverlayHandler.cs

@@ -5,4 +5,5 @@ namespace PixiEditor.Models.Handlers;
 public interface IPathOverlayHandler : IHandler
 {
     public void Show(VectorPath path);
+    public event Action<VectorPath> PathChanged; 
 }

+ 4 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -236,6 +236,10 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         TransformViewModel.TransformMoved += (_, args) => Internals.ChangeController.TransformMovedInlet(args);
         
         PathOverlayViewModel = new(this, Internals);
+        PathOverlayViewModel.PathChanged += path =>
+        {
+            Internals.ChangeController.PathOverlayChangedInlet(path);
+        };
         
         LineToolOverlayViewModel = new();
         LineToolOverlayViewModel.LineMoved += (_, args) =>

+ 9 - 2
src/PixiEditor/ViewModels/Document/TransformOverlays/PathOverlayViewModel.cs

@@ -14,8 +14,16 @@ internal class PathOverlayViewModel : ObservableObject, IPathOverlayHandler
     public VectorPath Path
     {
         get => path;
-        set => SetProperty(ref path, value);
+        set
+        {
+            if (SetProperty(ref path, value))
+            {
+                PathChanged?.Invoke(value);
+            }
+        }
     }
+
+    public event Action<VectorPath>? PathChanged;
     
     public PathOverlayViewModel(DocumentViewModel documentViewModel, DocumentInternalParts internals)
     {
@@ -23,7 +31,6 @@ internal class PathOverlayViewModel : ObservableObject, IPathOverlayHandler
         this.internals = internals;
     }
 
-
     public void Show(VectorPath path)
     {
         Path = path;        

+ 1 - 1
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -344,7 +344,7 @@ internal class ViewportOverlays
     {
         Binding pathBinding = new()
         {
-            Source = Viewport, Path = "Document.PathOverlayViewModel.Path", Mode = BindingMode.OneWay
+            Source = Viewport, Path = "Document.PathOverlayViewModel.Path", Mode = BindingMode.TwoWay
         };
 
         vectorPathOverlay.Bind(VectorPathOverlay.PathProperty, pathBinding);

+ 1 - 0
src/PixiEditor/Views/Overlays/Handles/AnchorHandle.cs

@@ -19,6 +19,7 @@ public class AnchorHandle : RectangleHandle
         StrokePaint.Style = PaintStyle.Stroke;
     }
 
+
     public override void Draw(Canvas context)
     {
         paint.StrokeWidth = (float)(1.0 / ZoomScale);

+ 15 - 3
src/PixiEditor/Views/Overlays/Handles/Handle.cs

@@ -17,6 +17,7 @@ using Path = Avalonia.Controls.Shapes.Path;
 namespace PixiEditor.Views.Overlays.Handles;
 
 public delegate void HandleEvent(Handle source, VecD position);
+
 public abstract class Handle : IHandle
 {
     public Paint? FillPaint { get; set; } = GetPaint("HandleBackgroundBrush");
@@ -32,16 +33,20 @@ public abstract class Handle : IHandle
     public event Action<Handle> OnRelease;
     public event Action<Handle> OnHover;
     public event Action<Handle> OnExit;
+    public event Action<Handle> OnTap;
     public Cursor? Cursor { get; set; }
 
     private bool isPressed;
     private bool isHovered;
+    private bool moved;
 
     public Handle(IOverlay owner)
     {
         Owner = owner;
         Position = VecD.Zero;
-        Size = Application.Current.TryGetResource("HandleSize", out object size) ? new VecD((double)size) : new VecD(16);
+        Size = Application.Current.TryGetResource("HandleSize", out object size)
+            ? new VecD((double)size)
+            : new VecD(16);
 
         Owner.PointerPressedOverlay += OnPointerPressed;
         Owner.PointerMovedOverlay += OnPointerMoved;
@@ -59,7 +64,7 @@ public abstract class Handle : IHandle
 
     public static T? GetResource<T>(string key)
     {
-       return ResourceLoader.GetResource<T>(key); 
+        return ResourceLoader.GetResource<T>(key);
     }
 
     public static VectorPath GetHandleGeometry(string handleName)
@@ -77,7 +82,7 @@ public abstract class Handle : IHandle
 
     protected static Paint? GetPaint(string key, PaintStyle style = PaintStyle.Fill)
     {
-       return ResourceLoader.GetPaint(key, style);
+        return ResourceLoader.GetPaint(key, style);
     }
 
     private void OnPointerPressed(OverlayPointerArgs args)
@@ -93,6 +98,7 @@ public abstract class Handle : IHandle
         {
             args.Handled = true;
             OnPressed(args);
+            moved = false;
             OnPress?.Invoke(this, args.Point);
             isPressed = true;
             args.Pointer.Capture(Owner);
@@ -128,6 +134,7 @@ public abstract class Handle : IHandle
         }
 
         OnDrag?.Invoke(this, args.Point);
+        moved = true;
     }
 
     private void OnPointerReleased(OverlayPointerArgs args)
@@ -142,6 +149,11 @@ public abstract class Handle : IHandle
             isPressed = false;
             OnRelease?.Invoke(this);
             args.Pointer.Capture(null);
+            
+            if (!moved)
+            {
+                OnTap?.Invoke(this);
+            }
         }
     }
 }

+ 67 - 10
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -29,7 +29,7 @@ public class VectorPathOverlay : Overlay
     {
         dashedStroke.UpdateZoom((float)newZoom);
     }
-    
+
     public override void RenderOverlay(Canvas context, RectD canvasBounds)
     {
         if (Path is null)
@@ -44,32 +44,89 @@ public class VectorPathOverlay : Overlay
         RenderHandles(context, points);
     }
 
-    private void RenderHandles(Canvas context, VecF[] points)
+    private void RenderHandles(Canvas context, IReadOnlyList<VecF> points)
     {
-        for (int i = 0; i < points.Length; i++)
+        for (int i = 0; i < points.Count; i++)
         {
             pointsHandles[i].Position = new VecD(points[i].X, points[i].Y);
             pointsHandles[i].Draw(context);
         }
     }
 
-    private void AdjustHandles(VecF[] points)
+    private void AdjustHandles(IReadOnlyList<VecF> points)
     {
-        if (pointsHandles.Count != points.Length)
+        if (pointsHandles.Count != points.Count)
         {
-            if (pointsHandles.Count > points.Length)
+            if (pointsHandles.Count > points.Count)
             {
-                pointsHandles.RemoveRange(points.Length, pointsHandles.Count - points.Length);
-                Handles.RemoveRange(points.Length, Handles.Count - points.Length);
+                pointsHandles.RemoveRange(points.Count, pointsHandles.Count - points.Count);
+                Handles.RemoveRange(points.Count, Handles.Count - points.Count);
             }
             else
             {
-                for (int i = pointsHandles.Count; i < points.Length; i++)
+                for (int i = pointsHandles.Count; i < points.Count; i++)
                 {
-                    pointsHandles.Add(new AnchorHandle(this));
+                    var handle = new AnchorHandle(this);
+                    handle.OnDrag += HandleOnOnDrag;
+                    handle.OnTap += OnHandleTap;
+                    pointsHandles.Add(handle);
                     AddHandle(pointsHandles[i]);
                 }
             }
         }
     }
+
+    private void OnHandleTap(Handle handle)
+    {
+        VectorPath newPath = new VectorPath(Path);
+        
+        if(IsLastHandle(handle)) return;
+        
+        if (IsFirstHandle(handle))
+        {
+            newPath.Close();
+        }
+        else
+        {
+            VecD pos = handle.Position;
+            newPath.LineTo(new VecF((float)pos.X, (float)pos.Y));
+        }
+
+        Path = newPath;
+    }
+
+    private bool IsFirstHandle(Handle handle)
+    {
+        return pointsHandles.IndexOf((AnchorHandle)handle) == 0;
+    }
+    
+    private bool IsLastHandle(Handle handle)
+    {
+        return pointsHandles.IndexOf((AnchorHandle)handle) == pointsHandles.Count - 1;
+    }
+
+    private void HandleOnOnDrag(Handle source, VecD position)
+    {
+        var handle = (AnchorHandle)source;
+        var index = pointsHandles.IndexOf(handle);
+        VecF[] updatedPoints = Path.Points.ToArray();
+        updatedPoints[index] = new VecF((float)position.X, (float)position.Y);
+        VectorPath newPath = new VectorPath();
+
+        newPath.MoveTo(updatedPoints[0]);
+
+        for (var i = 1; i < updatedPoints.Length; i++)
+        {
+            var point = updatedPoints[i];
+            newPath.LineTo(point);
+        }
+
+        using var iterator = Path.CreateIterator(false);
+        if (iterator.IsCloseContour)
+        {
+            newPath.Close();
+        }
+
+        Path = newPath;
+    }
 }