Browse Source

Merge pull request #926 from PixiEditor/fixes/5.05.2025

Fixed snapping not taking into consideration structure
Krzysztof Krysiński 3 months ago
parent
commit
6a0cd9fbe5

+ 11 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/GraphUtils.cs

@@ -4,7 +4,8 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
 public static class GraphUtils
 {
-    public static Queue<IReadOnlyNode> CalculateExecutionQueue(IReadOnlyNode outputNode)
+    public static Queue<IReadOnlyNode> CalculateExecutionQueue(IReadOnlyNode outputNode,
+        Func<IInputProperty, bool>? branchFilter = null)
     {
         var finalQueue = new HashSet<IReadOnlyNode>();
         var queueNodes = new Queue<IReadOnlyNode>();
@@ -13,12 +14,12 @@ public static class GraphUtils
         while (queueNodes.Count > 0)
         {
             var node = queueNodes.Dequeue();
-            
+
             if (finalQueue.Contains(node))
             {
                 continue;
             }
-            
+
             bool canAdd = true;
 
             foreach (var input in node.InputProperties)
@@ -33,8 +34,13 @@ public static class GraphUtils
                     continue;
                 }
 
+                if (branchFilter != null && !branchFilter(input))
+                {
+                    continue;
+                }
+
                 canAdd = false;
-                
+
                 if (finalQueue.Contains(input.Connection.Node))
                 {
                     finalQueue.Remove(input.Connection.Node);
@@ -46,7 +52,7 @@ public static class GraphUtils
                     queueNodes.Enqueue(input.Connection.Node);
                 }
             }
-            
+
             if (canAdd)
             {
                 finalQueue.Add(node);

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyNode.cs

@@ -28,7 +28,7 @@ public interface IReadOnlyNode : ICacheable
     ///     Traverses the graph backwards from this node. Backwards means towards the input nodes.
     /// </summary>
     /// <param name="action">The action to perform on each node. Input property is the input that was used to traverse this node.</param>
-    public void TraverseBackwards(Func<IReadOnlyNode, IInputProperty, bool> action);
+    public void TraverseBackwards(Func<IReadOnlyNode, IInputProperty, bool> action, Func<IInputProperty, bool>? branchCondition = null);
 
     /// <summary>
     ///     Traverses the graph forwards from this node. Forwards means towards the output nodes.

+ 53 - 29
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs

@@ -143,28 +143,32 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
     public override RectD? GetTightBounds(KeyFrameTime frameTime)
     {
         RectD? bounds = null;
+        if (!IsVisible.Value)
+            return null;
+
         if (Content.Connection != null)
         {
-            Content.Connection.Node.TraverseBackwards((n) =>
-            {
-                if (n is StructureNode structureNode)
+            Content.Connection.Node.TraverseBackwards(
+                (n, input) =>
                 {
-                    RectD? childBounds = structureNode.GetTightBounds(frameTime);
-                    if (childBounds != null)
+                    if (n is StructureNode { IsVisible.Value: true } structureNode)
                     {
-                        if (bounds == null)
-                        {
-                            bounds = childBounds;
-                        }
-                        else
+                        RectD? childBounds = structureNode.GetTightBounds(frameTime);
+                        if (childBounds != null)
                         {
-                            bounds = bounds.Value.Union(childBounds.Value);
+                            if (bounds == null)
+                            {
+                                bounds = childBounds;
+                            }
+                            else
+                            {
+                                bounds = bounds.Value.Union(childBounds.Value);
+                            }
                         }
                     }
-                }
 
-                return true;
-            });
+                    return true;
+                }, FilterInvisibleFolders);
 
             return bounds ?? RectD.Empty;
         }
@@ -177,26 +181,27 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
         RectD? bounds = null;
         if (Content.Connection != null)
         {
-            Content.Connection.Node.TraverseBackwards((n) =>
-            {
-                if (n is StructureNode structureNode)
+            Content.Connection.Node.TraverseBackwards(
+                (n, input) =>
                 {
-                    RectD? childBounds = structureNode.GetApproxBounds(frameTime);
-                    if (childBounds != null)
+                    if (n is StructureNode { IsVisible.Value: true } structureNode)
                     {
-                        if (bounds == null)
+                        RectD? childBounds = structureNode.GetApproxBounds(frameTime);
+                        if (childBounds != null)
                         {
-                            bounds = childBounds;
-                        }
-                        else
-                        {
-                            bounds = bounds.Value.Union(childBounds.Value);
+                            if (bounds == null)
+                            {
+                                bounds = childBounds;
+                            }
+                            else
+                            {
+                                bounds = bounds.Value.Union(childBounds.Value);
+                            }
                         }
                     }
-                }
 
-                return true;
-            });
+                    return true;
+                }, FilterInvisibleFolders);
 
             return bounds ?? RectD.Empty;
         }
@@ -204,6 +209,19 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
         return null;
     }
 
+    private bool FilterInvisibleFolders(IInputProperty input)
+    {
+        if (input is
+            {
+                Node: IReadOnlyFolderNode folderNode, InternalPropertyName: FolderNode.ContentInternalName
+            })
+        {
+            return folderNode.IsVisible.Value;
+        }
+
+        return true;
+    }
+
     public HashSet<Guid> GetLayerNodeGuids()
     {
         HashSet<Guid> guids = new();
@@ -240,10 +258,16 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
 
         if (Content.Connection != null)
         {
-            var executionQueue = GraphUtils.CalculateExecutionQueue(Content.Connection.Node);
+            var executionQueue = GraphUtils.CalculateExecutionQueue(Content.Connection.Node, FilterInvisibleFolders);
             while (executionQueue.Count > 0)
             {
                 IReadOnlyNode node = executionQueue.Dequeue();
+
+                if (node is IReadOnlyStructureNode { IsVisible.Value: false })
+                {
+                    continue;
+                }
+
                 if (node is IPreviewRenderable previewRenderable)
                 {
                     previewRenderable.RenderPreview(renderOn, context, elementToRenderName);

+ 10 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -49,7 +49,16 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
     public override RectD? GetApproxBounds(KeyFrameTime frameTime)
     {
-        return (RectD?)GetLayerImageAtFrame(frameTime.Frame).FindChunkAlignedCommittedBounds();
+        var chunkAlignedBounds = GetLayerImageAtFrame(frameTime.Frame).FindChunkAlignedCommittedBounds();
+        if (chunkAlignedBounds == null)
+        {
+            return null;
+        }
+
+        RectD size = new RectD(chunkAlignedBounds.Value.X, chunkAlignedBounds.Value.Y,
+            Math.Min(chunkAlignedBounds.Value.Width, layerImage.LatestSize.X),
+            Math.Min(chunkAlignedBounds.Value.Height, layerImage.LatestSize.Y));
+        return size;
     }
 
     protected internal override void DrawLayerInScene(SceneObjectRenderContext ctx,

+ 5 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -113,7 +113,7 @@ public abstract class Node : IReadOnlyNode, IDisposable
         lastContentCacheHash = GetContentCacheHash();
     }
 
-    public void TraverseBackwards(Func<IReadOnlyNode, IInputProperty, bool> action)
+    public void TraverseBackwards(Func<IReadOnlyNode, IInputProperty, bool> action, Func<IInputProperty, bool>? branchCondition = null)
     {
         var visited = new HashSet<IReadOnlyNode>();
         var queueNodes = new Queue<(IReadOnlyNode, IInputProperty)>();
@@ -135,6 +135,10 @@ public abstract class Node : IReadOnlyNode, IDisposable
 
             foreach (var inputProperty in node.Item1.InputProperties)
             {
+                if (branchCondition != null && !branchCondition(inputProperty))
+                {
+                    continue;
+                }
                 if (inputProperty.Connection != null)
                 {
                     queueNodes.Enqueue((inputProperty.Connection.Node, inputProperty));

+ 54 - 8
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -24,6 +24,7 @@ using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Layers;
 using Drawie.Numerics;
 using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ViewModels.Document.Nodes;
 using PixiEditor.ViewModels.Nodes;
@@ -79,7 +80,7 @@ internal class DocumentUpdater
                 ProcessUpdateStructureMemberName(info);
                 break;
             case StructureMemberIsVisible_ChangeInfo info:
-                ProcessUpdateStructureMemberIsVisible(info);
+                ProcessUpdateStructureMemberIsVisible(info.Id, info.IsVisible);
                 break;
             case StructureMemberOpacity_ChangeInfo info:
                 ProcessUpdateStructureMemberOpacity(info);
@@ -455,11 +456,24 @@ internal class DocumentUpdater
         doc.InternalRaiseLayersChanged(new LayersChangedEventArgs(info.Id, LayerAction.Remove));
     }
 
-    private void ProcessUpdateStructureMemberIsVisible(StructureMemberIsVisible_ChangeInfo info)
+    private void ProcessUpdateStructureMemberIsVisible(Guid id, bool isVisible)
     {
-        IStructureMemberHandler? memberVM = doc.StructureHelper.FindOrThrow(info.Id);
-        memberVM.SetIsVisible(info.IsVisible);
-        if (info.IsVisible)
+        IStructureMemberHandler? memberVM = doc.StructureHelper.FindOrThrow(id);
+        memberVM.SetIsVisible(isVisible);
+        UpdateMemberSnapping(memberVM);
+    }
+
+    private void UpdateMemberSnapping(IStructureMemberHandler memberVM)
+    {
+        List<IStructureMemberHandler>? children = null;
+        if (memberVM is IFolderHandler folder)
+        {
+            children = doc.StructureHelper.GetFolderChildren(folder.Id);
+        }
+
+        bool isTransformingMember = helper.ChangeController.TryGetExecutorFeature<ITransformableExecutor>()?
+            .IsTransformingMember(memberVM.Id) ?? false;
+        if (memberVM.IsVisibleStructurally && !isTransformingMember)
         {
             doc.SnappingHandler.AddFromBounds(memberVM.Id.ToString(), () => memberVM.TightBounds ?? RectD.Empty);
         }
@@ -467,6 +481,25 @@ internal class DocumentUpdater
         {
             doc.SnappingHandler.Remove(memberVM.Id.ToString());
         }
+
+        if (children != null)
+        {
+            foreach (IStructureMemberHandler child in children)
+            {
+                isTransformingMember = helper.ChangeController.TryGetExecutorFeature<ITransformableExecutor>()?
+                    .IsTransformingMember(child.Id) ?? false;
+
+                if (child.IsVisibleStructurally && !isTransformingMember)
+                {
+                    doc.SnappingHandler.AddFromBounds(child.Id.ToString(),
+                        () => child.TightBounds ?? RectD.Empty);
+                }
+                else
+                {
+                    doc.SnappingHandler.Remove(child.Id.ToString());
+                }
+            }
+        }
     }
 
     private void ProcessUpdateStructureMemberName(StructureMemberName_ChangeInfo info)
@@ -570,7 +603,8 @@ internal class DocumentUpdater
                 removedInputs.Add(input);
             }
 
-            if(info.Inputs.FirstOrDefault(x => x.PropertyName == input.PropertyName && x.ValueType != input.PropertyType) is { } changedInput)
+            if (info.Inputs.FirstOrDefault(x =>
+                    x.PropertyName == input.PropertyName && x.ValueType != input.PropertyType) is { } changedInput)
             {
                 removedInputs.Add(input);
             }
@@ -602,7 +636,9 @@ internal class DocumentUpdater
                 removedOutputs.Add(output);
             }
 
-            if(info.Outputs.FirstOrDefault(x => x.PropertyName == output.PropertyName && x.ValueType != output.Value.GetType()) is { } changedOutput)
+            if (info.Outputs.FirstOrDefault(x =>
+                    x.PropertyName == output.PropertyName && x.ValueType != output.Value.GetType()) is
+                { } changedOutput)
             {
                 removedOutputs.Add(output);
             }
@@ -747,6 +783,16 @@ internal class DocumentUpdater
             throw new MissingNodeException("Connection requested for a node that doesn't exist");
 #endif
         }
+
+        if (inputNode is IStructureMemberHandler structureMember)
+        {
+            UpdateMemberSnapping(structureMember);
+        }
+
+        if (outputNode is IStructureMemberHandler structureMember2)
+        {
+            UpdateMemberSnapping(structureMember2);
+        }
     }
 
     private void ProcessNodePosition(NodePosition_ChangeInfo info)
@@ -779,7 +825,7 @@ internal class DocumentUpdater
         {
             if (info.Property == StructureNode.IsVisiblePropertyName)
             {
-                structureMemberHandler.SetIsVisible((bool)info.Value);
+                ProcessUpdateStructureMemberIsVisible(structureMemberHandler.Id, (bool)info.Value);
             }
             else if (info.Property == StructureNode.OpacityPropertyName)
             {

+ 22 - 1
src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections;
+using System.Collections.Generic;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
 
@@ -254,4 +255,24 @@ internal class DocumentStructureModule
 
         return result;
     }
+
+    public List<IStructureMemberHandler> GetFolderChildren(Guid folderId)
+    {
+        List<IStructureMemberHandler> children = new List<IStructureMemberHandler>();
+
+        INodeHandler folder = FindNode<INodeHandler>(folderId);
+        var connectionInput = folder?.Inputs.FirstOrDefault(x => x.PropertyName == FolderNode.ContentInternalName);
+        if (folder == null || connectionInput?.ConnectedOutput == null)
+            return children;
+
+        connectionInput.ConnectedOutput.Node.TraverseBackwards(node =>
+        {
+            if (node is IStructureMemberHandler structureMemberNode)
+                children.Add(structureMemberNode);
+
+            return true;
+        });
+
+        return children;
+    }
 }

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

@@ -1,6 +1,7 @@
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
+using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 
@@ -11,4 +12,5 @@ public interface ITransformableExecutor : IExecutorFeature
     public void OnTransformApplied();
     public void OnLineOverlayMoved(VecD start, VecD end);
     public void OnSelectedObjectNudged(VecI distance);
+    public bool IsTransformingMember(Guid id);
 }

+ 16 - 8
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PasteImageExecutor.cs

@@ -30,7 +30,7 @@ internal class PasteImageExecutor : UpdateableChangeExecutor, ITransformableExec
         this.memberGuid = memberGuid;
         this.drawOnMask = drawOnMask;
     }
-    
+
     public override ExecutionState Start()
     {
         if (memberGuid == null)
@@ -40,37 +40,45 @@ internal class PasteImageExecutor : UpdateableChangeExecutor, ITransformableExec
             if (member is null)
                 return ExecutionState.Error;
             drawOnMask = member is not ILayerHandler layer || layer.ShouldDrawOnMask;
-            
+
             switch (drawOnMask)
             {
                 case true when !member.HasMaskBindable:
                 case false when member is not ILayerHandler:
                     return ExecutionState.Error;
             }
-            
+
             memberGuid = member.Id;
         }
 
         ShapeCorners corners = new(new RectD(pos, image.Size));
         internals!.ActionAccumulator.AddActions(
             new ClearSelection_Action(),
-            new PasteImage_Action(image, corners, memberGuid.Value, false, drawOnMask, document.AnimationHandler.ActiveFrameBindable, default));
-        document.TransformHandler.ShowTransform(DocumentTransformMode.Scale_Rotate_Shear_Perspective, true, corners, true);
+            new PasteImage_Action(image, corners, memberGuid.Value, false, drawOnMask,
+                document.AnimationHandler.ActiveFrameBindable, default));
+        document.TransformHandler.ShowTransform(DocumentTransformMode.Scale_Rotate_Shear_Perspective, true, corners,
+            true);
 
         return ExecutionState.Success;
     }
 
-    public bool IsTransforming => true; 
+    public bool IsTransforming => true;
 
     public void OnTransformChanged(ShapeCorners corners)
     {
-        internals!.ActionAccumulator.AddActions(new PasteImage_Action(image, corners, memberGuid.Value, false, drawOnMask, document!.AnimationHandler.ActiveFrameBindable, default));
+        internals!.ActionAccumulator.AddActions(new PasteImage_Action(image, corners, memberGuid.Value, false,
+            drawOnMask, document!.AnimationHandler.ActiveFrameBindable, default));
     }
 
     public void OnLineOverlayMoved(VecD start, VecD end) { }
 
     public void OnSelectedObjectNudged(VecI distance) => document!.TransformHandler.Nudge(distance);
 
+    public bool IsTransformingMember(Guid id)
+    {
+        return id == memberGuid;
+    }
+
     public void OnMidChangeUndo() => document!.TransformHandler.Undo();
 
     public void OnMidChangeRedo() => document!.TransformHandler.Redo();
@@ -95,7 +103,7 @@ internal class PasteImageExecutor : UpdateableChangeExecutor, ITransformableExec
         Type featureType = typeof(T);
         if (featureType == typeof(ITransformableExecutor))
             return IsTransforming;
-        
+
         if (featureType == typeof(IMidChangeUndoableExecutor))
             return true;
 

+ 6 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs

@@ -176,6 +176,11 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
     {
     }
 
+    public bool IsTransformingMember(Guid id)
+    {
+        return id == memberId && IsTransforming;
+    }
+
     public override void ForceStop()
     {
         StopMode(activeMode);
@@ -241,7 +246,7 @@ internal abstract class SimpleShapeToolExecutor : UpdateableChangeExecutor,
             foreach (var id in disabledSnappingMembers)
             {
                 var member = document.StructureHelper.Find(id);
-                if (member != null && member.IsVisibleBindable)
+                if (member is { IsVisibleStructurally: true })
                 {
                     document.SnappingHandler.AddFromBounds(id.ToString(), () => member?.TightBounds ?? RectD.Empty);
                 }

+ 4 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformReferenceLayerExecutor.cs

@@ -30,6 +30,10 @@ internal class TransformReferenceLayerExecutor : UpdateableChangeExecutor, ITran
     public void OnLineOverlayMoved(VecD start, VecD end) { }
 
     public void OnSelectedObjectNudged(VecI distance) => document!.TransformHandler.Nudge(distance);
+    public bool IsTransformingMember(Guid id)
+    {
+        return false;
+    }
 
     public void OnMidChangeUndo() => document!.TransformHandler.Undo();
 

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

@@ -418,6 +418,13 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
     public void OnLineOverlayMoved(VecD start, VecD end) { }
 
     public void OnSelectedObjectNudged(VecI distance) => document!.TransformHandler.Nudge(distance);
+    public bool IsTransformingMember(Guid id)
+    {
+        if (document!.SelectedStructureMember is null)
+            return false;
+
+        return selectedMembers.Contains(id) && IsTransforming;
+    }
 
     public void OnMidChangeUndo() => document!.TransformHandler.Undo();
 
@@ -484,7 +491,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
                 continue;
             }
 
-            if (member is ILayerHandler layer)
+            if (member is ILayerHandler layer && layer.IsVisibleStructurally)
             {
                 document!.SnappingHandler.AddFromBounds(layer.Id.ToString(), () => layer.TightBounds ?? RectD.Empty);
             }
@@ -496,7 +503,7 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         foreach (var id in disabledSnappingMembers)
         {
             var member = document!.StructureHelper.Find(id);
-            if (member is null)
+            if (member is null || !member.IsVisibleStructurally)
             {
                 continue;
             }

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

@@ -23,6 +23,7 @@ internal interface IStructureMemberHandler : INodeHandler
     public bool IsVisibleBindable { get; set; }
     public RectD? TightBounds { get; }
     public ShapeCorners TransformationCorners { get; }
+    public bool IsVisibleStructurally { get; }
     public void SetMaskIsVisible(bool infoIsVisible);
     public void SetClipToMemberBelowEnabled(bool infoClipToMemberBelow);
     public void SetBlendMode(BlendMode infoBlendMode);

+ 2 - 2
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -289,7 +289,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         };
         LayersChanged += (sender, args) =>
         {
-            if (args.LayerChangeType == LayerAction.Add)
+            /*if (args.LayerChangeType == LayerAction.Add)
             {
                 IReadOnlyStructureNode layer = Internals.Tracker.Document.FindMember(args.LayerAffectedGuid);
                 if (layer is not null)
@@ -301,7 +301,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             else if (args.LayerChangeType == LayerAction.Remove)
             {
                 SnappingViewModel.Remove(args.LayerAffectedGuid.ToString());
-            }
+            }*/
         };
 
         ReferenceLayerViewModel = new(this, Internals);

+ 23 - 0
src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs

@@ -47,6 +47,29 @@ internal abstract class StructureMemberViewModel<T> : NodeViewModel<T>, IStructu
     public ShapeCorners TransformationCorners => Internals.Tracker.Document.FindMember(Id)
         ?.GetTransformationCorners(Document.AnimationDataViewModel.ActiveFrameBindable) ?? new ShapeCorners();
 
+    public bool IsVisibleStructurally
+    {
+        get
+        {
+            if (!IsVisibleBindable)
+                return false;
+
+            bool visible = true;
+            TraverseForwards((node, previous, output, input) =>
+            {
+                if (node is IFolderHandler parent && input is { PropertyName: FolderNode.ContentInternalName })
+                {
+                    visible = parent.IsVisibleBindable;
+                    return visible;
+                }
+
+                return true;
+            });
+
+            return visible;
+        }
+    }
+
     public void SetMaskIsVisible(bool maskIsVisible)
     {
         this.maskIsVisible = maskIsVisible;

+ 25 - 25
src/PixiEditor/ViewModels/Document/StructureTree.cs

@@ -6,7 +6,7 @@ namespace PixiEditor.ViewModels.Document;
 internal class StructureTree
 {
     public ObservableCollection<IStructureMemberHandler> Members { get; } = new();
-   
+
     private Dictionary<INodeHandler, ObservableCollection<IStructureMemberHandler>> _memberMap = new();
 
     public void Update(NodeGraphViewModel nodeGraphViewModel)
@@ -17,7 +17,7 @@ internal class StructureTree
             _memberMap.Clear();
             return;
         }
-        
+
         int relativeFolderIndex = 0;
         List<IStructureMemberHandler> membersMet = new();
         ObservableCollection<IStructureMemberHandler> lastRoot = Members;
@@ -31,12 +31,12 @@ internal class StructureTree
                     relativeFolderIndex = lastRoot.Count;
                 }
             }
-            
-            if(node is IStructureMemberHandler structureMember)
+
+            if (node is IStructureMemberHandler structureMember)
             {
                 membersMet.Add(structureMember);
             }
-            
+
             if (previous is IFolderHandler folder)
             {
                 if (property.PropertyName == "Content")
@@ -45,63 +45,63 @@ internal class StructureTree
                     relativeFolderIndex = 0;
                 }
             }
-            
+
             if (node is IFolderHandler handler)
             {
                 UpdateMember(handler, relativeFolderIndex, lastRoot);
                 relativeFolderIndex++;
             }
+
             if (node is ILayerHandler layerHandler)
             {
                 UpdateMember(layerHandler, relativeFolderIndex, lastRoot);
                 relativeFolderIndex++;
             }
-            
+
             _memberMap.TryAdd(node, lastRoot);
-            
+
             return true;
         });
-        
+
         List<IStructureMemberHandler> toRemove = new();
-        
+
         foreach (var member in _memberMap)
         {
             if (!membersMet.Contains(member.Key))
             {
-                if(member.Key is not IStructureMemberHandler structureMemberHandler) continue;
+                if (member.Key is not IStructureMemberHandler structureMemberHandler) continue;
                 toRemove.Add(structureMemberHandler);
                 member.Value.Remove(structureMemberHandler);
             }
         }
-        
+
         foreach (var member in toRemove)
         {
             _memberMap.Remove(member);
         }
     }
-    
-    private void UpdateMember(IStructureMemberHandler member, int relativeIndex, ObservableCollection<IStructureMemberHandler> root)
+
+    private void UpdateMember(IStructureMemberHandler member, int relativeIndex,
+        ObservableCollection<IStructureMemberHandler> root)
     {
         bool existsInMembers = _memberMap.ContainsKey(member);
-        if(!existsInMembers)
+        if (!existsInMembers)
         {
             int clampIndex = Math.Clamp(relativeIndex, 0, root.Count);
             root.Insert(clampIndex, member);
             _memberMap.Add(member, root);
             return;
         }
-        else
+
+        ObservableCollection<IStructureMemberHandler> oldRoot = _memberMap[member];
+        if (oldRoot != root)
         {
-            ObservableCollection<IStructureMemberHandler> oldRoot = _memberMap[member];
-            if (oldRoot != root)
-            {
-                oldRoot.Remove(member);
-                int clampIndex = Math.Clamp(relativeIndex, 0, root.Count);
-                root.Insert(clampIndex, member);
-                _memberMap[member] = root;
-            }
+            oldRoot.Remove(member);
+            int clampIndex = Math.Clamp(relativeIndex, 0, root.Count);
+            root.Insert(clampIndex, member);
+            _memberMap[member] = root;
         }
-            
+
         bool existsAtIndex = root.Count > relativeIndex && root[relativeIndex] == member;
         if (!existsAtIndex) //TODO: this is inefficient, causes a lot of reordering, make the algorithm better
         {