Browse Source

Fixed self snapping issue

Krzysztof Krysiński 4 months ago
parent
commit
31699cc5e2

+ 14 - 8
src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 
 
 namespace PixiEditor.Models.DocumentModels.Public;
 namespace PixiEditor.Models.DocumentModels.Public;
@@ -99,16 +100,21 @@ internal class DocumentStructureModule
         return result;
         return result;
     }
     }
 
 
-    public (IStructureMemberHandler?, INodeHandler?) FindChildAndParent(Guid childGuid)
+    public List<IStructureMemberHandler> GetParents(Guid child)
     {
     {
-        List<IStructureMemberHandler>? path = FindPath(childGuid);
-        return path.Count switch
+        var childNode = FindNode<IStructureMemberHandler>(child);
+        if (childNode == null)
+            return new List<IStructureMemberHandler>();
+
+        List<IStructureMemberHandler> parents = new List<IStructureMemberHandler>();
+        childNode.TraverseForwards((node, previous, output, input) =>
         {
         {
-            0 => (null, null),
-            1 => (path[0], null),
-            >= 2 => (path[0], path[1]),
-            _ => (null, null),
-        };
+            if (node is IStructureMemberHandler parent && input is { PropertyName: FolderNode.ContentInternalName })
+                parents.Add(parent);
+            return true;
+        });
+
+        return parents;
     }
     }
 
 
     public (IStructureMemberHandler, IFolderHandler) FindChildAndParentOrThrow(Guid childGuid)
     public (IStructureMemberHandler, IFolderHandler) FindChildAndParentOrThrow(Guid childGuid)

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs

@@ -358,7 +358,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             if (noMovement)
             if (noMovement)
             {
             {
                 internals!.ActionAccumulator.AddFinishedActions(EndDrawAction());
                 internals!.ActionAccumulator.AddFinishedActions(EndDrawAction());
-                AddMemberToSnapping();
+                AddMembersToSnapping();
 
 
                 base.OnLeftMouseButtonUp(argsPositionOnCanvas);
                 base.OnLeftMouseButtonUp(argsPositionOnCanvas);
                 onEnded?.Invoke(this);
                 onEnded?.Invoke(this);

+ 1 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs

@@ -170,7 +170,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
         if (!startedDrawing)
         if (!startedDrawing)
         {
         {
             internals.ActionAccumulator.AddFinishedActions(EndDraw());
             internals.ActionAccumulator.AddFinishedActions(EndDraw());
-            AddMemberToSnapping();
+            AddMembersToSnapping();
             
             
             base.OnLeftMouseButtonUp(argsPositionOnCanvas);
             base.OnLeftMouseButtonUp(argsPositionOnCanvas);
             ActiveMode = ShapeToolMode.Preview;
             ActiveMode = ShapeToolMode.Preview;

+ 49 - 17
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Utils;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
@@ -26,7 +27,7 @@ namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 ///         - Transform -> Preview (when user applies the transform)
 ///         - Transform -> Preview (when user applies the transform)
 ///         - Transform -> Drawing (when user clicks outside of shape transform bounds)
 ///         - Transform -> Drawing (when user clicks outside of shape transform bounds)
 /// </summary>
 /// </summary>
-internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor, 
+internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
     ITransformableExecutor, IMidChangeUndoableExecutor, IDelayedColorSwapFeature
     ITransformableExecutor, IMidChangeUndoableExecutor, IDelayedColorSwapFeature
 {
 {
     private ShapeToolMode activeMode;
     private ShapeToolMode activeMode;
@@ -41,11 +42,14 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
             StartMode(activeMode);
             StartMode(activeMode);
         }
         }
     }
     }
+
     protected virtual bool AlignToPixels { get; } = true;
     protected virtual bool AlignToPixels { get; } = true;
-    
+
     protected Guid memberId;
     protected Guid memberId;
     protected VecD startDrawingPos;
     protected VecD startDrawingPos;
 
 
+    private IDisposable restoreSnapping;
+
     public override bool BlocksOtherActions => ActiveMode == ShapeToolMode.Drawing;
     public override bool BlocksOtherActions => ActiveMode == ShapeToolMode.Drawing;
 
 
     public override ExecutionState Start()
     public override ExecutionState Start()
@@ -66,7 +70,7 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
             ActiveMode = ShapeToolMode.Preview;
             ActiveMode = ShapeToolMode.Preview;
         }
         }
 
 
-        document.SnappingHandler.Remove(memberId.ToString()); // This disables self-snapping
+        restoreSnapping = DisableSelfSnapping(memberId, document);
 
 
         return ExecutionState.Success;
         return ExecutionState.Success;
     }
     }
@@ -144,17 +148,16 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
         ActiveMode = ShapeToolMode.Transform;
         ActiveMode = ShapeToolMode.Transform;
     }
     }
 
 
-    public bool IsTransforming => ActiveMode == ShapeToolMode.Transform; 
+    public bool IsTransforming => ActiveMode == ShapeToolMode.Transform;
 
 
     public virtual void OnTransformChanged(ShapeCorners corners)
     public virtual void OnTransformChanged(ShapeCorners corners)
     {
     {
-        
     }
     }
 
 
     public virtual void OnTransformApplied()
     public virtual void OnTransformApplied()
     {
     {
         ActiveMode = ShapeToolMode.Preview;
         ActiveMode = ShapeToolMode.Preview;
-        AddMemberToSnapping();
+        AddMembersToSnapping();
         HighlightSnapping(null, null);
         HighlightSnapping(null, null);
     }
     }
 
 
@@ -169,7 +172,7 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
     public override void ForceStop()
     public override void ForceStop()
     {
     {
         StopMode(activeMode);
         StopMode(activeMode);
-        AddMemberToSnapping();
+        AddMembersToSnapping();
         HighlightSnapping(null, null);
         HighlightSnapping(null, null);
     }
     }
 
 
@@ -180,15 +183,15 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
         document.SnappingHandler.SnappingController.HighlightedPoint = null;
         document.SnappingHandler.SnappingController.HighlightedPoint = null;
     }
     }
 
 
-    protected void AddMemberToSnapping()
+    protected void AddMembersToSnapping()
     {
     {
-        var member = document.StructureHelper.Find(memberId);
-        document!.SnappingHandler.AddFromBounds(memberId.ToString(), () => member?.TightBounds ?? RectD.Empty);
+        restoreSnapping?.Dispose();
     }
     }
-    
+
     protected VecD SnapAndHighlight(VecD pos)
     protected VecD SnapAndHighlight(VecD pos)
     {
     {
-        VecD snapped = document.SnappingHandler.SnappingController.GetSnapPoint(pos, out string snapX, out string snapY);
+        VecD snapped =
+            document.SnappingHandler.SnappingController.GetSnapPoint(pos, out string snapX, out string snapY);
         HighlightSnapping(snapX, snapY);
         HighlightSnapping(snapX, snapY);
         return snapped;
         return snapped;
     }
     }
@@ -209,12 +212,41 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
             document.SnappingHandler.SnappingController.HighlightedPoint = null;
             document.SnappingHandler.SnappingController.HighlightedPoint = null;
         }
         }
     }
     }
-    
+
+    public static IDisposable DisableSelfSnapping(Guid memberId, IDocument document)
+    {
+        List<Guid> disabledSnappingMembers = new();
+        disabledSnappingMembers.Add(memberId);
+        document.SnappingHandler.Remove(memberId.ToString());
+
+        Guid child = memberId;
+
+        var parents = document.StructureHelper.GetParents(child);
+
+        foreach (var parent in parents)
+        {
+            disabledSnappingMembers.Add(parent.Id);
+            document.SnappingHandler.Remove(parent.Id.ToString());
+        }
+
+        return Disposable.Create(() =>
+        {
+            foreach (var id in disabledSnappingMembers)
+            {
+                var member = document.StructureHelper.Find(id);
+                if (member != null && member.IsVisibleBindable)
+                {
+                    document.SnappingHandler.AddFromBounds(id.ToString(), () => member?.TightBounds ?? RectD.Empty);
+                }
+            }
+        });
+    }
+
     protected virtual void PrecisePositionChangeDrawingMode(VecD pos) { }
     protected virtual void PrecisePositionChangeDrawingMode(VecD pos) { }
     protected virtual void PrecisePositionChangeTransformMode(VecD pos) { }
     protected virtual void PrecisePositionChangeTransformMode(VecD pos) { }
     public abstract void OnMidChangeUndo();
     public abstract void OnMidChangeUndo();
     public abstract void OnMidChangeRedo();
     public abstract void OnMidChangeRedo();
-    public abstract bool CanUndo { get; } 
+    public abstract bool CanUndo { get; }
     public abstract bool CanRedo { get; }
     public abstract bool CanRedo { get; }
 
 
     public virtual bool IsFeatureEnabled(IExecutorFeature feature)
     public virtual bool IsFeatureEnabled(IExecutorFeature feature)
@@ -223,17 +255,17 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
         {
         {
             return IsTransforming;
             return IsTransforming;
         }
         }
-        
+
         if (feature is IMidChangeUndoableExecutor)
         if (feature is IMidChangeUndoableExecutor)
         {
         {
             return ActiveMode == ShapeToolMode.Transform;
             return ActiveMode == ShapeToolMode.Transform;
         }
         }
-        
+
         if (feature is IDelayedColorSwapFeature)
         if (feature is IDelayedColorSwapFeature)
         {
         {
             return true;
             return true;
         }
         }
-        
+
         return false;
         return false;
     }
     }
 }
 }

+ 29 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs

@@ -35,6 +35,8 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
     private bool movedOnce;
     private bool movedOnce;
     private bool duplicateOnStop = false;
     private bool duplicateOnStop = false;
 
 
+    private List<Guid> disabledSnappingMembers = new();
+
     public TransformSelectedExecutor(bool toolLinked)
     public TransformSelectedExecutor(bool toolLinked)
     {
     {
         Type = toolLinked ? ExecutorType.ToolLinked : ExecutorType.Regular;
         Type = toolLinked ? ExecutorType.ToolLinked : ExecutorType.Regular;
@@ -117,6 +119,17 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         foreach (var structureMemberHandler in members)
         foreach (var structureMemberHandler in members)
         {
         {
             document.SnappingHandler.Remove(structureMemberHandler.Id.ToString());
             document.SnappingHandler.Remove(structureMemberHandler.Id.ToString());
+            disabledSnappingMembers.Add(structureMemberHandler.Id);
+            var parents = document.StructureHelper.GetParents(structureMemberHandler.Id);
+
+            foreach (var parent in parents)
+            {
+                document.SnappingHandler.Remove(parent.Id.ToString());
+                if (!disabledSnappingMembers.Contains(parent.Id))
+                {
+                    disabledSnappingMembers.Add(parent.Id);
+                }
+            }
         }
         }
 
 
         selectedMembers = members.Select(m => m.Id).ToList();
         selectedMembers = members.Select(m => m.Id).ToList();
@@ -426,7 +439,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         internals!.ActionAccumulator.AddActions(new EndTransformSelected_Action());
         internals!.ActionAccumulator.AddActions(new EndTransformSelected_Action());
         internals!.ActionAccumulator.AddFinishedActions();
         internals!.ActionAccumulator.AddFinishedActions();
         document!.TransformHandler.HideTransform();
         document!.TransformHandler.HideTransform();
-        AddSnappingForMembers(memberCorners.Keys.ToList());
+        RestoreSnapping();
         onEnded!.Invoke(this);
         onEnded!.Invoke(this);
 
 
         if (Type == ExecutorType.ToolLinked)
         if (Type == ExecutorType.ToolLinked)
@@ -449,7 +462,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         internals!.ActionAccumulator.AddActions(new EndTransformSelected_Action());
         internals!.ActionAccumulator.AddActions(new EndTransformSelected_Action());
         internals!.ActionAccumulator.AddFinishedActions();
         internals!.ActionAccumulator.AddFinishedActions();
         document!.TransformHandler.HideTransform();
         document!.TransformHandler.HideTransform();
-        AddSnappingForMembers(memberCorners.Keys.ToList());
+        RestoreSnapping();
 
 
         isInProgress = false;
         isInProgress = false;
         document.TransformHandler.PassthroughPointerPressed -= OnLeftMouseButtonDown;
         document.TransformHandler.PassthroughPointerPressed -= OnLeftMouseButtonDown;
@@ -481,6 +494,20 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         }
         }
     }
     }
 
 
+    private void RestoreSnapping()
+    {
+        foreach (var id in disabledSnappingMembers)
+        {
+            var member = document!.StructureHelper.Find(id);
+            if (member is null)
+            {
+                continue;
+            }
+
+            document!.SnappingHandler.AddFromBounds(id.ToString(), () => member?.TightBounds ?? RectD.Empty);
+        }
+    }
+
     public bool IsFeatureEnabled(IExecutorFeature feature)
     public bool IsFeatureEnabled(IExecutorFeature feature)
     {
     {
         return feature is ITransformableExecutor && IsTransforming || feature is IMidChangeUndoableExecutor ||
         return feature is ITransformableExecutor && IsTransforming || feature is IMidChangeUndoableExecutor ||

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

@@ -34,6 +34,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     private IFillableShapeToolbar toolbar;
     private IFillableShapeToolbar toolbar;
     private IColorsHandler colorHandler;
     private IColorsHandler colorHandler;
     private bool isValidPathLayer;
     private bool isValidPathLayer;
+    private IDisposable restoreSnapping;
 
 
     public override ExecutorType Type => ExecutorType.ToolLinked;
     public override ExecutorType Type => ExecutorType.ToolLinked;
 
 
@@ -107,7 +108,7 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
             return ExecutionState.Error;
             return ExecutionState.Error;
         }
         }
 
 
-        document.SnappingHandler.Remove(member.Id.ToString()); // This disables self-snapping
+        restoreSnapping = SimpleShapeToolExecutor.DisableSelfSnapping(member.Id, document);
         return ExecutionState.Success;
         return ExecutionState.Success;
     }
     }
 
 
@@ -182,10 +183,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     public override void ForceStop()
     public override void ForceStop()
     {
     {
         document.PathOverlayHandler.Hide();
         document.PathOverlayHandler.Hide();
-        if (member.IsVisibleBindable)
-        {
-            document.SnappingHandler.AddFromBounds(member.Id.ToString(), () => member.TightBounds ?? RectD.Empty);
-        }
+
+        restoreSnapping?.Dispose();
 
 
         HighlightSnapping(null, null);
         HighlightSnapping(null, null);
         internals.ActionAccumulator.AddFinishedActions(new EndSetShapeGeometry_Action());
         internals.ActionAccumulator.AddFinishedActions(new EndSetShapeGeometry_Action());

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

@@ -30,4 +30,5 @@ public interface INodeHandler : INotifyPropertyChanged
     public void TraverseForwards(Func<INodeHandler, bool> func);
     public void TraverseForwards(Func<INodeHandler, bool> func);
     public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func);
     public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func);
     public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);
     public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func);
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func);
 }
 }

+ 56 - 22
src/PixiEditor/ViewModels/Nodes/NodeViewModel.cs

@@ -47,7 +47,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             }
             }
         }
         }
     }
     }
-    
+
     public string Category { get; }
     public string Category { get; }
 
 
     public string NodeNameBindable
     public string NodeNameBindable
@@ -60,7 +60,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 Internals.ActionAccumulator.AddFinishedActions(
                 Internals.ActionAccumulator.AddFinishedActions(
                     new SetNodeName_Action(Id, value));
                     new SetNodeName_Action(Id, value));
             }
             }
-        } 
+        }
     }
     }
 
 
     public string InternalName { get; private set; }
     public string InternalName { get; private set; }
@@ -71,11 +71,12 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         {
         {
             if (categoryBrush == null)
             if (categoryBrush == null)
             {
             {
-                if (!string.IsNullOrWhiteSpace(Category) && Application.Current.Styles.TryGetResource($"{Stylize(Category)}CategoryBackgroundBrush", App.Current.ActualThemeVariant, out var brushObj) && brushObj is IBrush brush)
+                if (!string.IsNullOrWhiteSpace(Category) &&
+                    Application.Current.Styles.TryGetResource($"{Stylize(Category)}CategoryBackgroundBrush",
+                        App.Current.ActualThemeVariant, out var brushObj) && brushObj is IBrush brush)
                 {
                 {
                     categoryBrush = brush;
                     categoryBrush = brush;
                 }
                 }
-
             }
             }
 
 
             return categoryBrush;
             return categoryBrush;
@@ -83,7 +84,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             string Stylize(string input) => string.Concat(input[0].ToString().ToUpper(), input.ToLower().AsSpan(1));
             string Stylize(string input) => string.Concat(input[0].ToString().ToUpper(), input.ToLower().AsSpan(1));
         }
         }
     }
     }
-    
+
     public NodeMetadata? Metadata { get; set; }
     public NodeMetadata? Metadata { get; set; }
 
 
     public VecD PositionBindable
     public VecD PositionBindable
@@ -117,7 +118,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         get => resultPainter;
         get => resultPainter;
         set => SetProperty(ref resultPainter, value);
         set => SetProperty(ref resultPainter, value);
     }
     }
-    
+
     public bool IsNodeSelected
     public bool IsNodeSelected
     {
     {
         get => isSelected;
         get => isSelected;
@@ -134,18 +135,19 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         Document = document;
         Document = document;
         Internals = internals;
         Internals = internals;
     }
     }
-    
+
     public virtual void OnInitialized() { }
     public virtual void OnInitialized() { }
-    
+
     public NodeViewModel()
     public NodeViewModel()
     {
     {
         var attribute = GetType().GetCustomAttribute<NodeViewModelAttribute>();
         var attribute = GetType().GetCustomAttribute<NodeViewModelAttribute>();
-        
+
         displayName = attribute.DisplayName;
         displayName = attribute.DisplayName;
         Category = attribute.Category;
         Category = attribute.Category;
     }
     }
 
 
-    public NodeViewModel(string nodeNameBindable, Guid id, VecD position, DocumentViewModel document, DocumentInternalParts internals)
+    public NodeViewModel(string nodeNameBindable, Guid id, VecD position, DocumentViewModel document,
+        DocumentInternalParts internals)
     {
     {
         this.nodeNameBindable = nodeNameBindable;
         this.nodeNameBindable = nodeNameBindable;
         this.id = id;
         this.id = id;
@@ -153,13 +155,13 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         Document = document;
         Document = document;
         Internals = internals;
         Internals = internals;
     }
     }
-    
+
     public void SetPosition(VecD newPosition)
     public void SetPosition(VecD newPosition)
     {
     {
         position = newPosition;
         position = newPosition;
         OnPropertyChanged(nameof(PositionBindable));
         OnPropertyChanged(nameof(PositionBindable));
     }
     }
-    
+
     public void SetName(string newName)
     public void SetName(string newName)
     {
     {
         nodeNameBindable = new LocalizedString(newName);
         nodeNameBindable = new LocalizedString(newName);
@@ -212,7 +214,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
             {
                 continue;
                 continue;
             }
             }
-            
+
             if (!func(node.Item1, node.Item2))
             if (!func(node.Item1, node.Item2))
             {
             {
                 return;
                 return;
@@ -223,7 +225,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 if (inputProperty.ConnectedOutput != null)
                 if (inputProperty.ConnectedOutput != null)
                 {
                 {
                     queueNodes.Enqueue((inputProperty.ConnectedOutput.Node, node.Item1));
                     queueNodes.Enqueue((inputProperty.ConnectedOutput.Node, node.Item1));
-                } 
+                }
             }
             }
         }
         }
     }
     }
@@ -242,7 +244,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
             {
                 continue;
                 continue;
             }
             }
-            
+
             if (!func(node.Item1, node.Item2, node.Item3))
             if (!func(node.Item1, node.Item2, node.Item3))
             {
             {
                 return;
                 return;
@@ -253,7 +255,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
                 if (inputProperty.ConnectedOutput != null)
                 if (inputProperty.ConnectedOutput != null)
                 {
                 {
                     queueNodes.Enqueue((inputProperty.ConnectedOutput.Node, node.Item1, inputProperty));
                     queueNodes.Enqueue((inputProperty.ConnectedOutput.Node, node.Item1, inputProperty));
-                } 
+                }
             }
             }
         }
         }
     }
     }
@@ -287,7 +289,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             }
             }
         }
         }
     }
     }
-    
+
     public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func)
     public void TraverseForwards(Func<INodeHandler, INodeHandler, bool> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
@@ -302,7 +304,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
             {
                 continue;
                 continue;
             }
             }
-            
+
             if (!func(node.Item1, node.Item2))
             if (!func(node.Item1, node.Item2))
             {
             {
                 return;
                 return;
@@ -317,7 +319,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             }
             }
         }
         }
     }
     }
-    
+
     public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func)
     public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, bool> func)
     {
     {
         var visited = new HashSet<INodeHandler>();
         var visited = new HashSet<INodeHandler>();
@@ -332,7 +334,7 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
             {
             {
                 continue;
                 continue;
             }
             }
-            
+
             if (!func(node.Item1, node.Item2, node.Item3))
             if (!func(node.Item1, node.Item2, node.Item3))
             {
             {
                 return;
                 return;
@@ -348,11 +350,41 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
         }
         }
     }
     }
 
 
+    public void TraverseForwards(Func<INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler, bool> func)
+    {
+        var visited = new HashSet<INodeHandler>();
+        var queueNodes = new Queue<(INodeHandler, INodeHandler, INodePropertyHandler, INodePropertyHandler)>();
+        queueNodes.Enqueue((this, null, null, null));
+
+        while (queueNodes.Count > 0)
+        {
+            var node = queueNodes.Dequeue();
+
+            if (!visited.Add(node.Item1))
+            {
+                continue;
+            }
+
+            if (!func(node.Item1, node.Item2, node.Item3, node.Item4))
+            {
+                return;
+            }
+
+            foreach (var outputProperty in node.Item1.Outputs)
+            {
+                foreach (var connection in outputProperty.ConnectedInputs)
+                {
+                    queueNodes.Enqueue((connection.Node, node.Item1, outputProperty, connection));
+                }
+            }
+        }
+    }
+
     public NodePropertyViewModel FindInputProperty(string propName)
     public NodePropertyViewModel FindInputProperty(string propName)
     {
     {
         return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;
         return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel;
     }
     }
-    
+
     public NodePropertyViewModel<T> FindInputProperty<T>(string propName)
     public NodePropertyViewModel<T> FindInputProperty<T>(string propName)
     {
     {
         return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel<T>;
         return Inputs.FirstOrDefault(x => x.PropertyName == propName) as NodePropertyViewModel<T>;
@@ -369,4 +401,6 @@ internal abstract class NodeViewModel : ObservableObject, INodeHandler
     }
     }
 }
 }
 
 
-internal abstract class NodeViewModel<T> : NodeViewModel where T : Node { }
+internal abstract class NodeViewModel<T> : NodeViewModel where T : Node
+{
+}

+ 1 - 0
src/PixiEditor/Views/Overlays/Overlay.cs

@@ -55,6 +55,7 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
     public event KeyEvent? KeyReleasedOverlay;
     public event KeyEvent? KeyReleasedOverlay;
 
 
     public Handle? CapturedHandle { get; set; } = null!;
     public Handle? CapturedHandle { get; set; } = null!;
+    public VecD PointerPosition { get; internal set; }
 
 
     private readonly Dictionary<AvaloniaProperty, OverlayTransition> activeTransitions = new();
     private readonly Dictionary<AvaloniaProperty, OverlayTransition> activeTransitions = new();
 
 

+ 3 - 3
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -733,7 +733,7 @@ public class VectorPathOverlay : Overlay
     {
     {
         var snappedPoint = SnappingController.GetSnapPoint(point, out string axisX, out string axisY);
         var snappedPoint = SnappingController.GetSnapPoint(point, out string axisX, out string axisY);
         var snapped = new VecD((float)snappedPoint.X, (float)snappedPoint.Y);
         var snapped = new VecD((float)snappedPoint.X, (float)snappedPoint.Y);
-        TryHighlightSnap(axisX, axisY);
+        TryHighlightSnap(axisX, axisY, snapped);
         return snapped;
         return snapped;
     }
     }
 
 
@@ -756,11 +756,11 @@ public class VectorPathOverlay : Overlay
         Refresh();
         Refresh();
     }
     }
 
 
-    private void TryHighlightSnap(string axisX, string axisY)
+    private void TryHighlightSnap(string axisX, string axisY, VecD? point = null)
     {
     {
         SnappingController.HighlightedXAxis = axisX;
         SnappingController.HighlightedXAxis = axisX;
         SnappingController.HighlightedYAxis = axisY;
         SnappingController.HighlightedYAxis = axisY;
-        SnappingController.HighlightedPoint = null;
+        SnappingController.HighlightedPoint = point;
     }
     }
 
 
     private AnchorHandle? GetHandleAt(int index)
     private AnchorHandle? GetHandleAt(int index)

+ 1 - 7
src/PixiEditor/Views/Overlays/SnappingOverlay.cs

@@ -28,7 +28,6 @@ internal class SnappingOverlay : Overlay
     private Paint horizontalAxisPen;
     private Paint horizontalAxisPen;
     private Paint verticalAxisPen; 
     private Paint verticalAxisPen; 
     private Paint previewPointPen;
     private Paint previewPointPen;
-    private VecD lastMousePosition;
 
 
     private Paint distanceTextPaint;
     private Paint distanceTextPaint;
     private Font distanceFont = Font.CreateDefault();
     private Font distanceFont = Font.CreateDefault();
@@ -59,7 +58,7 @@ internal class SnappingOverlay : Overlay
             return;
             return;
         }
         }
 
 
-        VecD mousePoint = SnappingController.HighlightedPoint ?? lastMousePosition;
+        VecD mousePoint = SnappingController.HighlightedPoint ?? PointerPosition;
 
 
         if (!string.IsNullOrEmpty(SnappingController.HighlightedXAxis))
         if (!string.IsNullOrEmpty(SnappingController.HighlightedXAxis))
         {
         {
@@ -111,11 +110,6 @@ internal class SnappingOverlay : Overlay
         context.DrawText($"{distance.Length.ToString("F2", CultureInfo.CurrentCulture)} px", center, distanceFont, distanceTextPaint);
         context.DrawText($"{distance.Length.ToString("F2", CultureInfo.CurrentCulture)} px", center, distanceFont, distanceTextPaint);
     }
     }
 
 
-    protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
-    {
-        lastMousePosition = args.Point;
-    }
-
     protected override void ZoomChanged(double newZoom)
     protected override void ZoomChanged(double newZoom)
     {
     {
         horizontalAxisPen.StrokeWidth = startSize / (float)newZoom;
         horizontalAxisPen.StrokeWidth = startSize / (float)newZoom;

+ 3 - 0
src/PixiEditor/Views/Rendering/Scene.cs

@@ -140,6 +140,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
 
     private PixelSize lastSize = PixelSize.Empty;
     private PixelSize lastSize = PixelSize.Empty;
     private Cursor lastCursor;
     private Cursor lastCursor;
+    private VecD lastMousePosition;
 
 
     public static readonly StyledProperty<string> RenderOutputProperty =
     public static readonly StyledProperty<string> RenderOutputProperty =
         AvaloniaProperty.Register<Scene, string>("RenderOutput");
         AvaloniaProperty.Register<Scene, string>("RenderOutput");
@@ -299,6 +300,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
                     continue;
                     continue;
                 }
                 }
 
 
+                overlay.PointerPosition = lastMousePosition;
                 overlay.ZoomScale = Scale;
                 overlay.ZoomScale = Scale;
 
 
                 if (!overlay.CanRender()) continue;
                 if (!overlay.CanRender()) continue;
@@ -331,6 +333,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         if (AllOverlays != null)
         if (AllOverlays != null)
         {
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
             OverlayPointerArgs args = ConstructPointerArgs(e);
+            lastMousePosition = args.Point;
 
 
             Cursor finalCursor = DefaultCursor;
             Cursor finalCursor = DefaultCursor;