Explorar el Código

Merge branch 'master' into posterization-node

Krzysztof Krysiński hace 2 días
padre
commit
b7b95abb35
Se han modificado 39 ficheros con 321 adiciones y 104 borrados
  1. 1 2
      src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs
  2. 22 24
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  3. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameData.cs
  4. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  5. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs
  6. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs
  7. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs
  8. 77 0
      src/PixiEditor.ChangeableDocument/Changes/Animation/CreateAnimationDataFromFolder_Change.cs
  9. 4 1
      src/PixiEditor.ChangeableDocument/Changes/Animation/CreateAnimationDataFromLayer_Change.cs
  10. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelected_UpdateableChange.cs
  11. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs
  12. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/Crop_Change.cs
  13. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs
  14. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs
  15. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs
  16. 62 14
      src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateFolder_Change.cs
  17. 1 0
      src/PixiEditor/Helpers/Constants/ClipboardDataFormats.cs
  18. 3 1
      src/PixiEditor/Helpers/SupportedFilesHelper.cs
  19. 2 0
      src/PixiEditor/Models/Commands/Attributes/Commands/ToolAttribute.cs
  20. 14 3
      src/PixiEditor/Models/Controllers/ClipboardController.cs
  21. 2 1
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  22. 7 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs
  23. 12 5
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs
  24. 2 2
      src/PixiEditor/Properties/AssemblyInfo.cs
  25. 9 0
      src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs
  26. 12 10
      src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs
  27. 22 2
      src/PixiEditor/ViewModels/SettingsWindowViewModel.cs
  28. 3 1
      src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs
  29. 36 1
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  30. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/RasterEllipseToolViewModel.cs
  31. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/RasterLineToolViewModel.cs
  32. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/RasterRectangleToolViewModel.cs
  33. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/VectorEllipseToolViewModel.cs
  34. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/VectorLineToolViewModel.cs
  35. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/VectorRectangleToolViewModel.cs
  36. 1 1
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml
  37. 1 1
      src/PixiEditor/Views/Main/MainTitleBar.axaml
  38. 1 1
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  39. 0 4
      src/PixiEditor/Views/Nodes/NodeGraphView.cs

+ 1 - 2
src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs

@@ -70,9 +70,8 @@ internal class DrawingSurfaceLineOperation : IMirroredDrawOperation
             newTo = (VecI)newTo.ReflectY((double)horAxisY).Round();
         }
 
-        Color color = paint.Paintable is ColorPaintable colorPaintable ? colorPaintable.Color : paint.Color;
 
-        return new DrawingSurfaceLineOperation(newFrom, newTo, paint.StrokeCap, paint.StrokeWidth, color, paint.BlendMode);
+        return new DrawingSurfaceLineOperation(newFrom, newTo, paint);
     }
 
     public void Dispose()

+ 22 - 24
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -40,11 +40,11 @@ internal class RectangleOperation : IMirroredDrawOperation
         surf.Canvas.RotateRadians((float)Data.Angle, (float)rect.Center.X, (float)rect.Center.Y);
 
         double maxRadiusInPx = Math.Min(Data.Size.X, Data.Size.Y) / 2;
-        double radiusInPx = Data.CornerRadius * maxRadiusInPx;
+        double radiusInPx = Data.CornerRadius * Math.Abs(maxRadiusInPx);
 
         if (Data.AntiAliasing)
         {
-            DrawAntiAliased(surf, rect, radiusInPx);
+            DrawAntiAliased(surf, rect, innerRect, radiusInPx);
         }
         else
         {
@@ -90,8 +90,24 @@ internal class RectangleOperation : IMirroredDrawOperation
         surf.Canvas.DrawPaintable(Data.Stroke, Data.BlendMode);
     }
 
-    private void DrawAntiAliased(DrawingSurface surf, RectD rect, double radius)
+    private void DrawAntiAliased(DrawingSurface surf, RectD rect, RectD innerRect, double radius)
     {
+        surf.Canvas.Save();
+        paint.StrokeWidth = Data.StrokeWidth > 0 ? Data.StrokeWidth : 1;
+        paint.SetPaintable(Data.StrokeWidth > 0 ? Data.Stroke : Data.FillPaintable);
+        paint.Style = PaintStyle.Fill;
+
+        if (radius == 0)
+        {
+            surf.Canvas.DrawRect((float)rect.Left, (float)rect.Top, (float)rect.Width,
+                (float)rect.Height, paint);
+        }
+        else
+        {
+            surf.Canvas.DrawRoundRect((float)rect.Left, (float)rect.Top, (float)rect.Width,
+                (float)rect.Height, (float)radius, (float)radius, paint);
+        }
+
         // draw fill
         if (Data.FillPaintable.AnythingVisible)
         {
@@ -102,34 +118,16 @@ internal class RectangleOperation : IMirroredDrawOperation
             paint.Style = PaintStyle.Fill;
             if (radius == 0)
             {
-                surf.Canvas.DrawRect((float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height, paint);
+                surf.Canvas.DrawRect((float)innerRect.Left, (float)innerRect.Top, (float)innerRect.Width, (float)innerRect.Height, paint);
             }
             else
             {
-                surf.Canvas.DrawRoundRect((float)rect.Left, (float)rect.Top, (float)rect.Width,
-                    (float)rect.Height, (float)radius, (float)radius, paint);
+                surf.Canvas.DrawRoundRect((float)innerRect.Left, (float)innerRect.Top, (float)innerRect.Width,
+                    (float)innerRect.Height, (float)radius, (float)radius, paint);
             }
 
             surf.Canvas.RestoreToCount(saved);
         }
-
-        // draw stroke
-        surf.Canvas.Save();
-        paint.StrokeWidth = Data.StrokeWidth > 0 ? Data.StrokeWidth : 1;
-        paint.SetPaintable(Data.StrokeWidth > 0 ? Data.Stroke : Data.FillPaintable);
-        paint.Style = PaintStyle.Stroke;
-        RectD innerRect = rect.Inflate(-Data.StrokeWidth / 2f);
-
-        if (radius == 0)
-        {
-            surf.Canvas.DrawRect((float)innerRect.Left, (float)innerRect.Top, (float)innerRect.Width,
-                (float)innerRect.Height, paint);
-        }
-        else
-        {
-            surf.Canvas.DrawRoundRect((float)innerRect.Left, (float)innerRect.Top, (float)innerRect.Width,
-                (float)innerRect.Height, (float)radius, (float)radius, paint);
-        }
     }
 
     public AffectedArea FindAffectedArea(VecI imageSize)

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrameData.cs

@@ -8,7 +8,7 @@ public class KeyFrameData : IDisposable, IReadOnlyKeyFrameData
 {
     public int StartFrame { get; set; }
     public int Duration { get; set; }
-    public Guid KeyFrameGuid { get; }
+    public Guid KeyFrameGuid { get; internal set; }
     public string AffectedElement { get; set; }
     public object Data { get; set; }
     public bool IsVisible { get; set; } = true;

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -444,12 +444,12 @@ internal class Document : IChangeable, IReadOnlyDocument
 
     private void ExtractLayers(FolderNode folder, List<Guid> list)
     {
-        List<Guid> result = new();
-        folder.TraverseBackwards(node =>
+        if(folder.Content.Connection == null) return;
+        folder.Content.Connection.Node.TraverseBackwards(node =>
         {
-            if (node is LayerNode layer && !result.Contains(layer.Id))
+            if (node is LayerNode layer && !list.Contains(layer.Id))
             {
-                result.Add(layer.Id);
+                list.Add(layer.Id);
             }
 
             return true;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs

@@ -21,7 +21,7 @@ public class EllipseVectorData : ShapeVectorData, IReadOnlyEllipseData
         RectD.FromCenterAndSize(Center, Radius * 2).Inflate(StrokeWidth / 2);
 
     public override ShapeCorners TransformationCorners =>
-        new ShapeCorners(VisualAABB).WithMatrix(TransformationMatrix);
+        new ShapeCorners(GeometryAABB).WithMatrix(TransformationMatrix);
 
 
     public EllipseVectorData(VecD center, VecD radius)

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs

@@ -15,7 +15,7 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
     public override RectD VisualAABB => GeometryAABB.Inflate(StrokeWidth / 2);
 
     public override ShapeCorners TransformationCorners =>
-        new ShapeCorners(VisualAABB).WithMatrix(TransformationMatrix);
+        new ShapeCorners(Path.TightBounds).WithMatrix(TransformationMatrix);
 
     public StrokeCap StrokeLineCap { get; set; } = StrokeCap.Round;
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs

@@ -27,7 +27,7 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
     }
 
     public override ShapeCorners TransformationCorners =>
-        new ShapeCorners(VisualAABB).WithMatrix(TransformationMatrix);
+        new ShapeCorners(GeometryAABB).WithMatrix(TransformationMatrix);
 
 
     public RectangleVectorData(VecD center, VecD size)

+ 77 - 0
src/PixiEditor.ChangeableDocument/Changes/Animation/CreateAnimationDataFromFolder_Change.cs

@@ -0,0 +1,77 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
+
+namespace PixiEditor.ChangeableDocument.Changes.Animation;
+
+internal class CreateAnimationDataFromFolder_Change : Change
+{
+    private readonly Guid folderGuid;
+    private Guid[] layerGuids;
+
+    [GenerateMakeChangeAction]
+    public CreateAnimationDataFromFolder_Change(Guid folderGuid)
+    {
+        this.folderGuid = folderGuid;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        if (!target.TryFindMember<FolderNode>(folderGuid, out FolderNode? layer))
+        {
+            return false;
+        }
+
+        var layers = target.ExtractLayers([layer.Id]);
+        if (layers.Count == 0) return false;
+
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
+        out bool ignoreInUndo)
+    {
+        FolderNode folder = target.FindNode(folderGuid) as FolderNode;
+        List<IChangeInfo> infos = new List<IChangeInfo>();
+        layerGuids = target.ExtractLayers([folder.Id]).ToArray();
+
+        foreach (var layer in layerGuids)
+        {
+            var node = target.FindNode(layer);
+            if(node is not LayerNode) continue;
+            foreach (var frame in node.KeyFrames)
+            {
+                if(frame.StartFrame == 0 && frame.Duration == 0) continue;
+                Guid keyFrameId = frame.KeyFrameGuid;
+                target.AnimationData.AddKeyFrame(new RasterKeyFrame(keyFrameId, node.Id, frame.StartFrame, target)
+                {
+                    Duration = frame.Duration,
+                    IsVisible = frame.IsVisible,
+                });
+                infos.Add(new CreateRasterKeyFrame_ChangeInfo(node.Id, frame.StartFrame, keyFrameId, true));
+                infos.Add(new KeyFrameLength_ChangeInfo(keyFrameId, frame.StartFrame, frame.Duration));
+                infos.Add(new KeyFrameVisibility_ChangeInfo(keyFrameId, frame.IsVisible));
+            }
+        }
+
+        ignoreInUndo = false;
+        return infos;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var layer = target.FindNode(folderGuid) as FolderNode;
+        List<IChangeInfo> infos = new List<IChangeInfo>();
+
+        var keyFrame = target.AnimationData.KeyFrames;
+        var ids = keyFrame.Where(x => x.NodeId == layer.Id || layerGuids.Contains(x.NodeId)).Select(x => x.Id).ToList();
+
+        foreach (var id in ids)
+        {
+            target.AnimationData.RemoveKeyFrame(id);
+            infos.Add(new DeleteKeyFrame_ChangeInfo(id));
+        }
+
+        return infos;
+    }
+}

+ 4 - 1
src/PixiEditor.ChangeableDocument/Changes/Animation/CreateAnimationDataFromLayer_Change.cs

@@ -30,9 +30,12 @@ internal class CreateAnimationDataFromLayer_Change : Change
             Guid keyFrameId = frame.KeyFrameGuid;
             target.AnimationData.AddKeyFrame(new RasterKeyFrame(keyFrameId, layer.Id, frame.StartFrame, target)
             {
-                Duration = frame.Duration
+                Duration = frame.Duration,
+                IsVisible = frame.IsVisible,
             });
             infos.Add(new CreateRasterKeyFrame_ChangeInfo(layer.Id, frame.StartFrame, keyFrameId, true));
+            infos.Add(new KeyFrameLength_ChangeInfo(keyFrameId, frame.StartFrame, frame.Duration));
+            infos.Add(new KeyFrameVisibility_ChangeInfo(keyFrameId, frame.IsVisible));
         }
 
         ignoreInUndo = false;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelected_UpdateableChange.cs

@@ -91,7 +91,7 @@ internal class TransformSelected_UpdateableChange : InterruptableUpdateableChang
 
         if (memberData.Count == 1 && firstLayer is VectorLayerNode vectorLayer)
         {
-            tightBounds = vectorLayer.EmbeddedShapeData?.VisualAABB ?? default;
+            tightBounds = vectorLayer.EmbeddedShapeData?.GeometryAABB ?? default;
         }
 
         for (var i = 1; i < memberData.Count; i++)

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs

@@ -52,9 +52,9 @@ internal class ClipCanvas_Change : ResizeBasedChangeBase
         {
             if (member is ImageLayerNode layer)
             {
-                layer.ForEveryFrame(img =>
+                layer.ForEveryFrame((img, id) =>
                 {
-                    Resize(img, layer.Id, size, -(VecI)newBounds.Pos, deletedChunks);
+                    Resize(img, id, size, -(VecI)newBounds.Pos, deletedChunks);
                 });
             }
             else if (member is ITransformableObject transformableObject)

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/Crop_Change.cs

@@ -37,9 +37,9 @@ internal class Crop_Change : ResizeBasedChangeBase
         {
             if (member is ImageLayerNode layer)
             {
-                layer.ForEveryFrame(frame =>
+                layer.ForEveryFrame((frame, id) =>
                 {
-                    Resize(frame, layer.Id, rect.Size, rect.Pos * -1, deletedChunks);
+                    Resize(frame, id, rect.Size, rect.Pos * -1, deletedChunks);
                 });
             }
             if (member.EmbeddedMask is null)

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs

@@ -52,10 +52,10 @@ internal abstract class ResizeBasedChangeBase : Change
         {
             if (member is ImageLayerNode layer)
             {
-                layer.ForEveryFrame(img =>
+                layer.ForEveryFrame((img, id) =>
                 {
                     img.EnqueueResize(_originalSize);
-                    foreach (var stored in deletedChunks[layer.Id])
+                    foreach (var stored in deletedChunks[id])
                         stored.ApplyChunksToImage(img);
                     img.CommitChanges();
                 });

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs

@@ -49,9 +49,9 @@ internal class ResizeCanvas_Change : ResizeBasedChangeBase
         {
             if (member is ImageLayerNode layer)
             {
-                layer.ForEveryFrame(img =>
+                layer.ForEveryFrame((img, id) =>
                 {
-                    Resize(img, layer.Id, newSize, offset, deletedChunks);
+                    Resize(img, id, newSize, offset, deletedChunks);
                 });
             }
 

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs

@@ -91,11 +91,11 @@ internal class ResizeImage_Change : Change
         {
             if (member is ImageLayerNode layer)
             {
-                layer.ForEveryFrame(img =>
+                layer.ForEveryFrame((img, id) =>
                 {
                     ScaleChunkyImage(img);
                     var affected = img.FindAffectedArea();
-                    savedChunks[layer.Id] = new CommittedChunkStorage(img, affected.Chunks);
+                    savedChunks[id] = new CommittedChunkStorage(img, affected.Chunks);
                     img.CommitChanges();
                 });
             }
@@ -127,11 +127,11 @@ internal class ResizeImage_Change : Change
         {
             if (member is ImageLayerNode layer)
             {
-                layer.ForEveryFrame(layerImage =>
+                layer.ForEveryFrame((layerImage, id) =>
                 {
                     layerImage.EnqueueResize(originalSize);
                     layerImage.EnqueueClear();
-                    savedChunks[layer.Id].ApplyChunksToImage(layerImage);
+                    savedChunks[id].ApplyChunksToImage(layerImage);
                     layerImage.CommitChanges();
                 });
             }

+ 62 - 14
src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateFolder_Change.cs

@@ -1,6 +1,7 @@
 using System.Collections.Immutable;
 using System.Collections.ObjectModel;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
@@ -17,6 +18,8 @@ internal class DuplicateFolder_Change : Change
     private Guid[] contentDuplicateGuids;
 
     private Guid[]? childGuidsToUse;
+    private Dictionary<Guid, List<Guid>> keyFramesMap = new();
+    private Dictionary<Guid, Guid> nodeMap = new();
 
     private ConnectionsData? connectionsData;
     private Dictionary<Guid, ConnectionsData> contentConnectionsData = new();
@@ -64,14 +67,15 @@ internal class DuplicateFolder_Change : Change
         List<IChangeInfo> operations = new();
 
         target.NodeGraph.AddNode(clone);
-        
+
         var previousConnection = targetInput.Connection;
 
         operations.Add(CreateNode_ChangeInfo.CreateFromNode(clone));
         operations.AddRange(NodeOperations.AppendMember(targetInput, clone.Output, clone.Background, clone.Id));
-        operations.AddRange(NodeOperations.AdjustPositionsAfterAppend(clone, targetInput.Node, previousConnection?.Node as Node, out originalPositions));
+        operations.AddRange(NodeOperations.AdjustPositionsAfterAppend(clone, targetInput.Node,
+            previousConnection?.Node as Node, out originalPositions));
 
-        DuplicateContent(target, clone, existingLayer, operations);
+        DuplicateContent(target, clone, existingLayer, operations, firstApply);
 
         ignoreInUndo = false;
 
@@ -116,29 +120,70 @@ internal class DuplicateFolder_Change : Change
     }
 
     private void DuplicateContent(Document target, FolderNode clone, FolderNode existingLayer,
-        List<IChangeInfo> operations)
+        List<IChangeInfo> operations, bool firstApply)
     {
-        Dictionary<Guid, Guid> nodeMap = new Dictionary<Guid, Guid>();
+        if (firstApply)
+        {
+            nodeMap = new Dictionary<Guid, Guid>();
+            nodeMap[existingLayer.Id] = clone.Id;
+        }
 
-        nodeMap[existingLayer.Id] = clone.Id;
         int counter = 0;
         List<Guid> contentGuidList = new();
 
+        if (firstApply)
+        {
+            keyFramesMap = new Dictionary<Guid, List<Guid>>();
+        }
+
+        int childCounter = 0;
+
         existingLayer.Content.Connection?.Node.TraverseBackwards(x =>
         {
             if (x is not Node targetNode)
                 return false;
 
             Node? node = targetNode.Clone();
-            
-            if(node is not FolderNode && childGuidsToUse is not null && counter < childGuidsToUse.Length)
+
+            if (contentDuplicateGuids != null && contentDuplicateGuids.Length > 0)
+            {
+                node.Id = contentDuplicateGuids[childCounter];
+                childCounter++;
+            }
+            else
             {
-                node.Id = childGuidsToUse[counter];
-                counter++;
+                if (node is not FolderNode && childGuidsToUse is not null && counter < childGuidsToUse.Length)
+                {
+                    node.Id = childGuidsToUse[counter];
+                    counter++;
+                }
+            }
+
+            if (firstApply)
+            {
+                keyFramesMap[node.Id] = new List<Guid>();
+                keyFramesMap[node.Id].AddRange(x.KeyFrames.Select(kf => kf.KeyFrameGuid));
+            }
+            else
+            {
+                if (keyFramesMap.TryGetValue(node.Id, out List<Guid>? keyFrameGuids))
+                {
+                    for (int i = 0; i < x.KeyFrames.Count; i++)
+                    {
+                        if (i < keyFrameGuids.Count)
+                        {
+                            var kf = x.KeyFrames[i] as KeyFrameData;
+                            kf.KeyFrameGuid = keyFrameGuids[i];
+                        }
+                    }
+                }
+            }
+
+            if (firstApply)
+            {
+                nodeMap[x.Id] = node.Id;
+                contentGuidList.Add(node.Id);
             }
-            
-            nodeMap[x.Id] = node.Id;
-            contentGuidList.Add(node.Id);
 
             target.NodeGraph.AddNode(node);
 
@@ -154,6 +199,9 @@ internal class DuplicateFolder_Change : Change
                 target.FindNodeOrThrow<Node>(targetNodeId), target.NodeGraph));
         }
 
-        contentDuplicateGuids = contentGuidList.ToArray();
+        if (firstApply)
+        {
+            contentDuplicateGuids = contentGuidList.ToArray();
+        }
     }
 }

+ 1 - 0
src/PixiEditor/Helpers/Constants/ClipboardDataFormats.cs

@@ -10,4 +10,5 @@ public static class ClipboardDataFormats
     public const string CelIdList = "PixiEditor.CelIdList";
     public const string PixiVectorData = "PixiEditor.VectorData";
     public const string UriList = "text/uri-list";
+    public const string HadSelectionFormat = "PixiEditor.HadSelection";
 }

+ 3 - 1
src/PixiEditor/Helpers/SupportedFilesHelper.cs

@@ -83,7 +83,9 @@ internal class SupportedFilesHelper
         if (file is null)
             return null;
 
-        string extension = Path.GetExtension(file.Path.LocalPath);
+        string? localPath = file.TryGetLocalPath();
+
+        string extension = Path.GetExtension(localPath ?? file.Name);
         return allSupportedExtensions.Single(i => i.CanSave && i.Extensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
     }
 

+ 2 - 0
src/PixiEditor/Models/Commands/Attributes/Commands/ToolAttribute.cs

@@ -10,6 +10,8 @@ internal partial class Command
         public Key Transient { get; set; }
         public bool TransientImmediate { get; set; } = false;
 
+        public string? CommonToolType { get; set; }
+
         public ToolAttribute() : base(null, null, null)
         {
         }

+ 14 - 3
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -68,6 +68,7 @@ internal static class ClipboardController
 
         Surface surfaceToCopy = null;
         RectD copyArea = RectD.Empty;
+        bool hadSelection = false;
 
         if (!document.SelectionPathBindable.IsEmpty)
         {
@@ -83,6 +84,7 @@ internal static class ClipboardController
 
             surfaceToCopy = surface.AsT2.Item1;
             copyArea = (RectD)surface.AsT2.Item2;
+            hadSelection = true;
         }
         else if (document.TransformViewModel.TransformActive || lastTransform != null)
         {
@@ -122,6 +124,11 @@ internal static class ClipboardController
             data.SetVecD(ClipboardDataFormats.PositionFormat, copyArea.Pos);
         }
 
+        if (hadSelection)
+        {
+            data.Set(ClipboardDataFormats.HadSelectionFormat, true);
+        }
+
         string[] layerIds = document.GetSelectedMembers().Select(x => x.ToString()).ToArray();
         string layerIdsString = string.Join(";", layerIds);
 
@@ -235,14 +242,18 @@ internal static class ClipboardController
         Guid[] layerIds = await GetLayerIds(data);
 
         bool hasPos = data.Any(x => x.Contains(ClipboardDataFormats.PositionFormat));
+        bool hadSelection = data.Any(x => x.Contains(ClipboardDataFormats.HadSelectionFormat));
 
         IDocument? targetDoc = manager.Documents.FirstOrDefault(x => x.Id == sourceDocument);
 
-        if (targetDoc != null && pasteAsNew && layerIds is { Length: > 0 } &&
+        if (targetDoc != null && !hadSelection && pasteAsNew && layerIds is { Length: > 0 } &&
             (!hasPos || await AllMatchesPos(layerIds, data, targetDoc)))
         {
             foreach (var layerId in layerIds)
             {
+                if (targetDoc.StructureHelper.Find(layerId) == null)
+                    continue;
+
                 if (sourceDocument == document.Id)
                 {
                     document.Operations.DuplicateMember(layerId);
@@ -495,7 +506,7 @@ internal static class ClipboardController
         {
             text = await importObj.GetDataAsync(DataFormats.Text) as string;
         }
-        catch(InvalidCastException ex) // bug on x11
+        catch (InvalidCastException ex) // bug on x11
         {
         }
 
@@ -848,7 +859,7 @@ internal static class ClipboardController
         data.Set(ClipboardDataFormats.DocumentFormat, Encoding.UTF8.GetBytes(docId.ToString()));
 
         byte[] idsBytes = Encoding.UTF8.GetBytes(string.Join(";", ids.Select(x => x.ToString())));
-        
+
         data.Set(format, idsBytes);
 
         await Clipboard.SetDataObjectAsync(data);

+ 2 - 1
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -235,7 +235,8 @@ internal class DocumentOperationsModule : IDocumentOperations
         {
             Internals.ActionAccumulator.AddFinishedActions(
                 new DuplicateFolder_Action(guidValue, newGuid, null),
-                new SetSelectedMember_PassthroughAction(newGuid));
+                new SetSelectedMember_PassthroughAction(newGuid),
+                new CreateAnimationDataFromFolder_Action(newGuid));
         }
 
         return newGuid;

+ 7 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs

@@ -14,6 +14,7 @@ using PixiEditor.ChangeableDocument;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changes.Vectors;
+using PixiEditor.Models.DocumentModels.Public;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.Views.Overlays.TransformOverlay;
 using Color = Drawie.Backend.Core.ColorsImpl.Color;
@@ -314,7 +315,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
         if (highlight)
         {
-            HighlightSnapAxis(snapXAxis, snapYAxis, string.IsNullOrEmpty(snapXAxis) && string.IsNullOrEmpty(snapYAxis) ? null : snapped);
+            HighlightSnapAxis(snapXAxis, snapYAxis,
+                string.IsNullOrEmpty(snapXAxis) && string.IsNullOrEmpty(snapYAxis) ? null : snapped);
         }
 
         if (AlignToPixels)
@@ -344,7 +346,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
         if (highlight)
         {
-            HighlightSnapAxis(snapXAxis, snapYAxis, string.IsNullOrEmpty(snapXAxis) && string.IsNullOrEmpty(snapYAxis) ? null : snapped);
+            HighlightSnapAxis(snapXAxis, snapYAxis,
+                string.IsNullOrEmpty(snapXAxis) && string.IsNullOrEmpty(snapYAxis) ? null : snapped);
         }
 
         if (snapped != VecI.Zero)
@@ -422,9 +425,10 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
                 if (SelectLayerOnTap)
                 {
                     var layersUnderCursor = QueryLayers<ILayerHandler>(argsPositionOnCanvas);
-                    var firstValidLayer = layersUnderCursor.FirstOrDefault(x => CanSelectLayer(x));
+                    var firstValidLayer = layersUnderCursor.FirstOrDefault(x => CanSelectLayer(x) && x.Id != memberId);
                     if (firstValidLayer != null)
                     {
+                        document.Operations.SetSelectedMember(Guid.Empty); // fixes reselecting
                         document.Operations.SetSelectedMember(firstValidLayer.Id);
                     }
                 }

+ 12 - 5
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs

@@ -84,11 +84,18 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
             toolbar.FillBrush = textData.FillPaintable.ToBrush();
             toolbar.StrokeBrush = textData.Stroke.ToBrush();
             toolbar.ToolSize = textData.StrokeWidth;
-            toolbar.FontFamily = textData.Font.Family;
-            toolbar.FontSize = textData.Font.Size;
-            toolbar.Spacing = textData.Spacing ?? textData.Font.Size;
-            toolbar.Bold = textData.Font.Bold;
-            toolbar.Italic = textData.Font.Italic;
+            try
+            {
+                toolbar.FontFamily = textData.Font.Family;
+                toolbar.FontSize = textData.Font.Size;
+                toolbar.Spacing = textData.Spacing ?? textData.Font.Size;
+                toolbar.Bold = textData.Font.Bold;
+                toolbar.Italic = textData.Font.Italic;
+            }
+            catch (InvalidOperationException) // Native font likely disposed
+            {
+                
+            }
 
             onPath = textData.Path;
             lastText = textData.Text;

+ 2 - 2
src/PixiEditor/Properties/AssemblyInfo.cs

@@ -43,5 +43,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.0.1.11")]
-[assembly: AssemblyFileVersion("2.0.1.11")]
+[assembly: AssemblyVersion("2.0.1.13")]
+[assembly: AssemblyFileVersion("2.0.1.13")]

+ 9 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -299,6 +299,12 @@ internal partial class DocumentViewModel
         float centerY = (float)rectangleData.Center.Y;
         float halfWidth = (float)rectangleData.Size.X / 2f;
         float halfHeight = (float)rectangleData.Size.Y / 2f;
+        float minHalf = Math.Min(halfWidth, halfHeight);
+        float clampedCorner = Math.Clamp((float)rectangleData.CornerRadius, 0f, 1f);
+        float radius = minHalf * clampedCorner;
+        float radiusX = Math.Min(radius, halfWidth);
+        float radiusY = Math.Min(radius, halfHeight);
+
 
         rect.X.Unit = SvgNumericUnit.FromUserUnits(centerX - halfWidth);
         rect.Y.Unit = SvgNumericUnit.FromUserUnits(centerY - halfHeight);
@@ -306,6 +312,9 @@ internal partial class DocumentViewModel
         rect.Width.Unit = SvgNumericUnit.FromUserUnits(rectangleData.Size.X);
         rect.Height.Unit = SvgNumericUnit.FromUserUnits(rectangleData.Size.Y);
 
+        rect.Rx.Unit = SvgNumericUnit.FromUserUnits(radiusX);
+        rect.Ry.Unit = SvgNumericUnit.FromUserUnits(radiusY);
+        
         return rect;
     }
 

+ 12 - 10
src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs

@@ -20,7 +20,7 @@ namespace PixiEditor.ViewModels.Document;
 internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposable
 {
     private bool isFullyCreated;
-    
+
     public DocumentViewModel DocumentViewModel { get; }
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
@@ -110,7 +110,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
         connection.OutputProperty.ConnectedInputs.Add(connection.InputProperty);
 
         Connections.Add(connection);
-        
+
         UpdatesFramesPartOf(connection.InputNode);
         UpdatesFramesPartOf(connection.OutputNode);
 
@@ -126,11 +126,11 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
             connection.InputProperty.ConnectedOutput = null;
             connection.OutputProperty.ConnectedInputs.Remove(connection.InputProperty);
             Connections.Remove(connection);
+
+            UpdatesFramesPartOf(connection.InputNode);
+            UpdatesFramesPartOf(connection.OutputNode);
         }
 
-        UpdatesFramesPartOf(connection.InputNode);
-        UpdatesFramesPartOf(connection.OutputNode);
-        
         var node = AllNodes.FirstOrDefault(x => x.Id == nodeId);
         if (node != null)
         {
@@ -152,7 +152,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
         var lastKnownFramesPartOf = node.Frames.OfType<NodeZoneViewModel>().ToHashSet();
         var startLookup = Frames.OfType<NodeZoneViewModel>().ToDictionary(x => x.Start);
         var currentlyPartOf = new HashSet<NodeZoneViewModel>();
-        
+
         node.TraverseBackwards(x =>
         {
             if (x is IPairNodeEndViewModel)
@@ -161,8 +161,10 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
             if (x is not IPairNodeStartViewModel)
                 return Traverse.Further;
 
-            var zone = startLookup[x];
-            currentlyPartOf.Add(zone);
+            if (startLookup != null && startLookup.TryGetValue(x, out var zone))
+            {
+                currentlyPartOf.Add(zone);
+            }
 
             return Traverse.Further;
         });
@@ -185,9 +187,9 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
     {
         if (isFullyCreated)
             return;
-        
+
         isFullyCreated = true;
-        
+
         foreach (var nodeZoneViewModel in Frames.OfType<NodeZoneViewModel>())
         {
             UpdateNodesPartOf(nodeZoneViewModel);

+ 22 - 2
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -95,9 +95,21 @@ internal partial class SettingsWindowViewModel : ViewModelBase
     [Command.Internal("PixiEditor.Shortcuts.Export")]
     public static async Task ExportShortcuts()
     {
+        IStorageFolder? suggestedStartLocation = null;
+        try
+        {
+            suggestedStartLocation =
+                await MainWindow.Current!.StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents);
+        }
+        catch (Exception)
+        {
+            // If we can't get the documents folder, we will just use the default location
+            // This is not a critical error, so we can ignore it
+        }
+
         var file = await MainWindow.Current!.StorageProvider.SaveFilePickerAsync(new()
         {
-            SuggestedStartLocation = await MainWindow.Current!.StorageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents),
+            SuggestedStartLocation = suggestedStartLocation,
             FileTypeChoices = new List<FilePickerFileType>()
             {
                 new FilePickerFileType("PixiShorts (*.pixisc)")
@@ -127,7 +139,15 @@ internal partial class SettingsWindowViewModel : ViewModelBase
         
         if (file is not null)
         {
-            File.Copy(CommandController.ShortcutsPath, file.Path.LocalPath, true);
+            try
+            {
+                File.Copy(CommandController.ShortcutsPath, file.Path.LocalPath, true);
+            }
+            catch (Exception ex)
+            {
+                string errMessageTrimmed = ex.Message.Length > 100 ? ex.Message[..100] + "..." : ex.Message;
+                NoticeDialog.Show(title: "ERROR", message: new LocalizedString("UNKNOWN_ERROR_SAVING").Value + $" {errMessageTrimmed}");
+            }
         }
         
         // Sometimes, focus was brought back to the last edited shortcut

+ 3 - 1
src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs

@@ -347,13 +347,15 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
     private void MoveSelectedMember(bool upwards)
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
-        var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
+        var member = doc?.SelectedStructureMember;
         if (member is null)
             return;
         var path = doc!.StructureHelper.FindPath(member.Id);
         if (path.Count < 2 || path[1] is not FolderNodeViewModel folderVm)
             return;
         var parent = folderVm;
+        if(parent.Children.Count == 0)
+            return;
         int curIndex = parent.Children.IndexOf(path[0]);
         if (upwards)
         {

+ 36 - 1
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -2,6 +2,7 @@
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Linq;
+using System.Reflection;
 using Avalonia.Input;
 using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.ChangeableDocument;
@@ -180,14 +181,36 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
     {
         ActiveTool?.OnToolDeselected(false);
         ActiveToolSet = toolSetHandler;
+        if (ActiveTool != null && !ActiveToolSet.Tools.Contains(ActiveTool))
+        {
+            TrySelectCommonToolInNewToolSet();
+        }
         ActiveToolSet.ApplyToolSetSettings();
         UpdateEnabledState();
 
+
         ActiveTool?.OnToolSelected(false);
 
         OnPropertyChanged(nameof(NonSelectedToolSets));
     }
 
+    private void TrySelectCommonToolInNewToolSet()
+    {
+        var commonTool = ActiveToolSet.Tools.FirstOrDefault(tool =>
+        {
+            var attr = tool.GetType().GetCustomAttribute<Command.ToolAttribute>();
+            if (attr is null) return false;
+
+            return ActiveTool?.GetType().GetCustomAttribute<Command.ToolAttribute>()?.CommonToolType ==
+                   attr.CommonToolType;
+        });
+
+        if (commonTool is not null)
+        {
+            SetActiveTool(commonTool, false);
+        }
+    }
+
     [Command.Basic("PixiEditor.Tools.ToggleSelectionTinting", "TOGGLE_TINTING_SELECTION", "TOGGLE_TINTING_SELECTION_DESCRIPTIVE", AnalyticsTrack = true)]
     public void ToggleTintSelection() => SelectionTintingEnabled = !SelectionTintingEnabled;
 
@@ -389,7 +412,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         if (foundTool == null)
         {
             foundTool = allTools.FirstOrDefault(x => x.GetType().IsAssignableFrom(toolType));
-            if(foundTool == null)
+            if(foundTool == null || SimilarToolInActiveToolSetExists(toolType))
                 return;
 
             var toolset = AllToolSets.FirstOrDefault(x => x.Tools.Contains(foundTool));
@@ -426,6 +449,18 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         }
     }
 
+    private bool SimilarToolInActiveToolSetExists(Type toolType)
+    {
+        Command.ToolAttribute attr = toolType.GetCustomAttribute<Command.ToolAttribute>();
+        if (attr is null) return false;
+
+        return ActiveToolSet.Tools.Any(tool =>
+        {
+            var toolAttr = tool.GetType().GetCustomAttribute<Command.ToolAttribute>();
+            return toolAttr is not null && toolAttr.CommonToolType == attr.CommonToolType;
+        });
+    }
+
     public void HandleToolRepeatShortcutDown()
     {
         if (ActiveTool == null) return;

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/RasterEllipseToolViewModel.cs

@@ -10,7 +10,7 @@ using PixiEditor.UI.Common.Localization;
 
 namespace PixiEditor.ViewModels.Tools.Tools;
 
-[Command.Tool(Key = Key.C)]
+[Command.Tool(Key = Key.C, CommonToolType = "Ellipse")]
 internal class RasterEllipseToolViewModel : ShapeTool, IRasterEllipseToolHandler
 {
     private string defaultActionDisplay = "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT";

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/RasterLineToolViewModel.cs

@@ -12,7 +12,7 @@ using PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
 
 namespace PixiEditor.ViewModels.Tools.Tools;
 
-[Command.Tool(Key = Key.L)]
+[Command.Tool(Key = Key.L, CommonToolType = "Line")]
 internal class RasterLineToolViewModel : ShapeTool, ILineToolHandler
 {
     private string defaultActionDisplay = "LINE_TOOL_ACTION_DISPLAY_DEFAULT";

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/RasterRectangleToolViewModel.cs

@@ -11,7 +11,7 @@ using PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
 
 namespace PixiEditor.ViewModels.Tools.Tools;
 
-[Command.Tool(Key = Key.R)]
+[Command.Tool(Key = Key.R, CommonToolType = "Rectangle")]
 internal class RasterRectangleToolViewModel : ShapeTool, IRasterRectangleToolHandler
 {
     private string defaultActionDisplay = "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT";

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/VectorEllipseToolViewModel.cs

@@ -12,7 +12,7 @@ using PixiEditor.UI.Common.Localization;
 
 namespace PixiEditor.ViewModels.Tools.Tools;
 
-[Command.Tool(Key = Key.C)]
+[Command.Tool(Key = Key.C, CommonToolType = "Ellipse")]
 internal class VectorEllipseToolViewModel : ShapeTool, IVectorEllipseToolHandler
 {
     public const string NewLayerKey = "NEW_ELLIPSE_LAYER_NAME";

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/VectorLineToolViewModel.cs

@@ -16,7 +16,7 @@ using PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
 
 namespace PixiEditor.ViewModels.Tools.Tools;
 
-[Command.Tool(Key = Key.L)]
+[Command.Tool(Key = Key.L, CommonToolType = "Line")]
 internal class VectorLineToolViewModel : ShapeTool, IVectorLineToolHandler
 {
     public const string NewLayerKey = "NEW_LINE_LAYER_NAME";

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/VectorRectangleToolViewModel.cs

@@ -13,7 +13,7 @@ using PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
 
 namespace PixiEditor.ViewModels.Tools.Tools;
 
-[Command.Tool(Key = Key.R)]
+[Command.Tool(Key = Key.R, CommonToolType = "Rectangle")]
 internal class VectorRectangleToolViewModel : ShapeTool, IVectorRectangleToolHandler
 {
     public const string NewLayerKey = "NEW_RECTANGLE_LAYER_NAME";

+ 1 - 1
src/PixiEditor/Views/Dock/DocumentTemplate.axaml

@@ -20,7 +20,7 @@
 
     <viewportControls:Viewport
         x:Name="Viewport"
-        CenterViewportTrigger="{Binding DockCenterViewportTrigger}"
+        CenterViewportTrigger="{Binding CenterViewportTrigger}"
         ZoomViewportTrigger="{Binding ZoomViewportTrigger}"
         MouseDownCommand="{Binding Path=IoSubViewModel.MouseDownCommand, Source={viewModels1:MainVM}}"
         MouseMoveCommand="{Binding Path=IoSubViewModel.MouseMoveCommand, Source={viewModels1:MainVM}}"

+ 1 - 1
src/PixiEditor/Views/Main/MainTitleBar.axaml

@@ -47,7 +47,7 @@
                         </OnPlatform.Default>
                     </OnPlatform>
                 </Panel.Margin>
-                <ToggleButton Name="LogoButton" Padding="0" BorderThickness="0">
+                <ToggleButton Name="LogoButton" Padding="0" BorderThickness="0" FlowDirection="LeftToRight">
                     <Interaction.Behaviors>
                         <behaviours:ShowFlyoutOnTrigger Trigger="{Binding OpenPixiEditorMenuTrigger}" />
                     </Interaction.Behaviors>

+ 1 - 1
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -174,7 +174,7 @@
             </Grid.RowDefinitions>
             <tools:Toolbar
                 DataContext="{Binding Source={viewModels:MainVM}, Path=.}" />
-            <StackPanel Margin="0, 5, 0, 0" Background="Transparent" Grid.Row="1" Orientation="Horizontal" Name="toolsetsPanel">
+            <StackPanel Margin="0, 5, 0, 0" Grid.Row="1" Orientation="Horizontal" Name="toolsetsPanel">
                 <Border ClipToBounds="False"
                         HorizontalAlignment="Left"
                         Padding="5 0"

+ 0 - 4
src/PixiEditor/Views/Nodes/NodeGraphView.cs

@@ -773,10 +773,6 @@ internal class NodeGraphView : Zoombox.Zoombox
                 {
                     connection = (endConnectionProperty, startConnectionProperty, null);
                 }
-                else
-                {
-                    return;
-                }
             }
         }