2
0
Эх сурвалжийг харах

Merge branch 'master' into fix/rounded-rectangle-bleeding

Krzysztof Krysiński 3 өдөр өмнө
parent
commit
91ae1c2640

+ 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;

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

@@ -0,0 +1,73 @@
+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)
+            {
+                Guid keyFrameId = frame.KeyFrameGuid;
+                target.AnimationData.AddKeyFrame(new RasterKeyFrame(keyFrameId, folder.Id, frame.StartFrame, target)
+                {
+                    Duration = frame.Duration
+                });
+                infos.Add(new CreateRasterKeyFrame_ChangeInfo(folder.Id, frame.StartFrame, keyFrameId, true));
+            }
+        }
+
+        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;
+    }
+}

+ 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();
+        }
     }
 }

+ 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 - 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;

+ 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;

+ 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;
     }
 

+ 8 - 8
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)
@@ -185,9 +185,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)
         {

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

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