瀏覽代碼

Added snapping

flabbet 9 月之前
父節點
當前提交
3813782606

+ 6 - 0
src/PixiEditor/Models/Controllers/InputDevice/SnappingController.cs

@@ -349,4 +349,10 @@ public class SnappingController
         yAxis = string.Empty;
         return pos;
     }
+
+    public void AddXYAxis(string identifier, Func<VecD> pointFunc)
+    {
+        HorizontalSnapPoints[identifier] = () => pointFunc().X;
+        VerticalSnapPoints[identifier] = () => pointFunc().Y;
+    }
 }

+ 6 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -23,11 +23,11 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor,
     private IBasicShapeToolbar toolbar;
 
     public override ExecutorType Type => ExecutorType.ToolLinked;
-    
+
     public bool CanUndo => document.PathOverlayHandler.HasUndo;
     public bool CanRedo => document.PathOverlayHandler.HasRedo;
 
-    public override bool BlocksOtherActions => false; 
+    public override bool BlocksOtherActions => false;
 
     public override ExecutionState Start()
     {
@@ -71,6 +71,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor,
             return ExecutionState.Error;
         }
 
+        document.SnappingHandler.Remove(member.Id.ToString()); // This disables self-snapping
         return ExecutionState.Success;
     }
 
@@ -80,10 +81,10 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor,
         {
             return;
         }
-        
+
         startingPath.LineTo((VecF)args.PositionOnCanvas);
         PathVectorData vectorData = ConstructShapeData();
-        
+
         internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(member.Id, vectorData));
     }
 
@@ -105,6 +106,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutor,
     public override void ForceStop()
     {
         document.PathOverlayHandler.Hide();
+        document.SnappingHandler.AddFromBounds(member.Id.ToString(), () => member.TightBounds ?? RectD.Empty);
         internals.ActionAccumulator.AddActions(new EndSetShapeGeometry_Action());
     }
 

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

@@ -8,4 +8,5 @@ public interface ISnappingHandler
     public SnappingController SnappingController { get; }
     public void Remove(string id);
     public void AddFromBounds(string id, Func<RectD> tightBounds);
+    public void AddFromPoint(string id, Func<VecD> func);
 }

+ 5 - 0
src/PixiEditor/ViewModels/Document/SnappingViewModel.cs

@@ -29,6 +29,11 @@ public class SnappingViewModel : PixiObservableObject, ISnappingHandler
         SnappingController.AddBounds(id, tightBounds);
     }
 
+    public void AddFromPoint(string id, Func<VecD> func)
+    {
+        SnappingController.AddXYAxis(id, func);
+    }
+
     public void Remove(string id)
     {
         SnappingController.RemoveAll(id);

+ 6 - 0
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -351,9 +351,15 @@ internal class ViewportOverlays
         {
             Source = Viewport, Path = "Document.PathOverlayViewModel.AddToUndoCommand", Mode = BindingMode.OneWay
         };
+        
+        Binding snappingBinding = new()
+        {
+            Source = Viewport, Path = "Document.SnappingViewModel.SnappingController", Mode = BindingMode.OneWay
+        };
 
         vectorPathOverlay.Bind(VectorPathOverlay.PathProperty, pathBinding);
         vectorPathOverlay.Bind(VectorPathOverlay.AddToUndoCommandProperty, addToUndoCommandBinding);
+        vectorPathOverlay.Bind(VectorPathOverlay.SnappingControllerProperty, snappingBinding);
     }
 
     private void BindSnappingOverlay()

+ 75 - 21
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -4,6 +4,7 @@ using Avalonia.Input;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using PixiEditor.Extensions.UI.Overlays;
+using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Views.Overlays.Drawables;
 using PixiEditor.Views.Overlays.Handles;
 using Canvas = Drawie.Backend.Core.Surfaces.Canvas;
@@ -16,6 +17,16 @@ public class VectorPathOverlay : Overlay
         AvaloniaProperty.Register<VectorPathOverlay, VectorPath>(
             nameof(Path));
 
+    public static readonly StyledProperty<SnappingController> SnappingControllerProperty =
+        AvaloniaProperty.Register<VectorPathOverlay, SnappingController>(
+            nameof(SnappingController));
+
+    public SnappingController SnappingController
+    {
+        get => GetValue(SnappingControllerProperty);
+        set => SetValue(SnappingControllerProperty, value);
+    }
+
     public VectorPath Path
     {
         get => GetValue(PathProperty);
@@ -38,6 +49,7 @@ public class VectorPathOverlay : Overlay
     static VectorPathOverlay()
     {
         AffectsOverlayRender(PathProperty);
+        PathProperty.Changed.Subscribe(OnPathChanged);
     }
 
     protected override void ZoomChanged(double newZoom)
@@ -59,6 +71,11 @@ public class VectorPathOverlay : Overlay
         RenderHandles(context, points);
     }
 
+    public override bool CanRender()
+    {
+        return Path != null;
+    }
+
     private void RenderHandles(Canvas context, IReadOnlyList<VecF> points)
     {
         for (int i = 0; i < points.Count; i++)
@@ -82,12 +99,7 @@ public class VectorPathOverlay : Overlay
             {
                 for (int i = Handles.Count; i < points.Count; i++)
                 {
-                    var handle = new AnchorHandle(this);
-                    handle.OnPress += OnHandlePress;
-                    handle.OnDrag += OnHandleDrag;
-                    handle.OnRelease += OnHandleRelease;
-                    handle.OnTap += OnHandleTap;
-                    AddHandle(handle);
+                    CreateHandle(i);
                 }
             }
         }
@@ -104,6 +116,8 @@ public class VectorPathOverlay : Overlay
             Handles.RemoveAt(i);
         }
 
+        SnappingController.RemoveAll("editingPath");
+
         for (int i = 0; i < points.Count; i++)
         {
             var handle = new AnchorHandle(this);
@@ -112,16 +126,28 @@ public class VectorPathOverlay : Overlay
             handle.OnRelease += OnHandleRelease;
             handle.OnTap += OnHandleTap;
             AddHandle(handle);
+            SnappingController.AddXYAxis($"editingPath[{i}]", () => handle.Position);
         }
     }
 
+    private void CreateHandle(int atIndex)
+    {
+        var handle = new AnchorHandle(this);
+        handle.OnPress += OnHandlePress;
+        handle.OnDrag += OnHandleDrag;
+        handle.OnRelease += OnHandleRelease;
+        handle.OnTap += OnHandleTap;
+        AddHandle(handle);
+        SnappingController.AddXYAxis($"editingPath[{atIndex}]", () => handle.Position);
+    }
+
     private void OnHandleTap(Handle handle, OverlayPointerArgs args)
     {
         if (Path.IsClosed)
         {
-            return;    
+            return;
         }
-        
+
         VectorPath newPath = new VectorPath(Path);
         if (args.Modifiers.HasFlag(KeyModifiers.Control)) return;
 
@@ -159,20 +185,14 @@ public class VectorPathOverlay : Overlay
             {
                 case PathVerb.Move:
                     point = data.points[0];
-                    if (i == index)
-                    {
-                        point = (VecF)args.Point;
-                    }
+                    point = TryApplyNewPos(args, i, index, point);
 
                     newPath.MoveTo(point);
                     i++;
                     break;
                 case PathVerb.Line:
                     point = data.points[1];
-                    if (i == index)
-                    {
-                        point = (VecF)args.Point;
-                    }
+                    point = TryApplyNewPos(args, i, index, point);
 
                     newPath.LineTo(point);
                     i++;
@@ -200,10 +220,41 @@ public class VectorPathOverlay : Overlay
         Path = newPath;
     }
 
+    private VecF TryApplyNewPos(OverlayPointerArgs args, int i, int index, VecF point)
+    {
+        if (i == index)
+        {
+            var snappedPoint = SnappingController.GetSnapPoint(args.Point, out string axisX, out string axisY);
+            point = new VecF((float)snappedPoint.X, (float)snappedPoint.Y);
+            TryHighlightSnap(axisX, axisY);
+        }
+
+        return point;
+    }
+
     private void OnHandlePress(Handle source, OverlayPointerArgs args)
     {
+        SnappingController.RemoveAll($"editingPath[{Handles.IndexOf(source)}]");
+    }
+
+    private void OnHandleRelease(Handle source, OverlayPointerArgs args)
+    {
+        AddToUndoCommand.Execute(Path);
+
+        SnappingController.AddXYAxis($"editingPath[{Handles.IndexOf(source)}]", () => source.Position);
+        
+        SnappingController.HighlightedXAxis = null;
+        SnappingController.HighlightedYAxis = null;
+        
+        Refresh();
     }
 
+    private void TryHighlightSnap(string axisX, string axisY)
+    {
+        SnappingController.HighlightedXAxis = axisX;
+        SnappingController.HighlightedYAxis = axisY;
+    }
+    
     private AnchorHandle GetHandleAt(int index)
     {
         if (index < 0 || index >= Handles.Count)
@@ -218,11 +269,14 @@ public class VectorPathOverlay : Overlay
 
         return null;
     }
-
-    private void OnHandleRelease(Handle source, OverlayPointerArgs args)
+    
+    private static void OnPathChanged(AvaloniaPropertyChangedEventArgs<VectorPath> args)
     {
-        AddToUndoCommand.Execute(Path);
-
-        Refresh();
+        if (args.NewValue.Value == null)
+        {
+            var overlay = args.Sender as VectorPathOverlay;
+            overlay.SnappingController.RemoveAll("editingPath");
+            overlay.Handles.Clear(); 
+        }
     }
 }