Browse Source

Fixed invalid node reference in keyframe

flabbet 1 year ago
parent
commit
4c50475fd3

+ 19 - 2
src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs

@@ -30,10 +30,25 @@ internal class AnimationData : IReadOnlyAnimationData
         else
         {
             var node = document.FindNodeOrThrow<Node>(id);
-            GroupKeyFrame createdGroup = new GroupKeyFrame(node, keyFrame.StartFrame, document);
+            GroupKeyFrame createdGroup = new GroupKeyFrame(node.Id, keyFrame.StartFrame, document);
             createdGroup.Children.Add(keyFrame);
             keyFrames.Add(createdGroup);
         }
+
+        SubscribeToKeyFrameEvents(keyFrame);
+    }
+
+    private void SubscribeToKeyFrameEvents(KeyFrame keyFrame)
+    {
+        Node node = document.FindNodeOrThrow<Node>(keyFrame.NodeId);
+
+        keyFrame.KeyFrameVisibilityChanged += node.SetKeyFrameVisibility;
+        keyFrame.KeyFrameLengthChanged += node.SetKeyFrameLength;
+    }
+    
+    private void UnsubscribeFromKeyFrameEvents(KeyFrame keyFrame)
+    {
+        keyFrame.ClearEvents();
     }
 
     public void RemoveKeyFrame(Guid createdKeyFrameId)
@@ -45,15 +60,17 @@ internal class AnimationData : IReadOnlyAnimationData
                 keyFrames.Remove(group);
                 foreach (var child in group.Children)
                 {
-                    RemoveKeyFrame(child.Id); 
+                    RemoveKeyFrame(child.Id);
                 }
             }
+
             if (document.TryFindNode<Node>(frame.NodeId, out Node? node))
             {
                 node.RemoveKeyFrame(frame.Id);
             }
 
             parent?.Children.Remove(frame);
+            UnsubscribeFromKeyFrameEvents(frame);
         });
     }
 

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs

@@ -32,15 +32,15 @@ internal class GroupKeyFrame : KeyFrame, IKeyFrameChildrenContainer
 
     IReadOnlyList<IReadOnlyKeyFrame> IKeyFrameChildrenContainer.Children => Children;
 
-    public GroupKeyFrame(Node node, int startFrame, Document document) : base(node, startFrame)
+    public GroupKeyFrame(Guid targetNodeId, int startFrame, Document document) : base(targetNodeId, startFrame)
     {
-        Id = node.Id;
+        Id = targetNodeId; 
         this.document = document;
     }
 
     public override KeyFrame Clone()
     {
-        var clone = new GroupKeyFrame(TargetNode, StartFrame, document) { Id = this.Id, IsVisible = IsVisible };
+        var clone = new GroupKeyFrame(NodeId, StartFrame, document) { Id = this.Id, IsVisible = IsVisible };
         foreach (var child in Children)
         {
             clone.Children.Add(child.Clone());

+ 16 - 10
src/PixiEditor.ChangeableDocument/Changeables/Animations/KeyFrame.cs

@@ -4,12 +4,17 @@ using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
+public delegate void KeyFrameVisibilityChangedHandler(Guid keyFrameId, bool isVisible);
+public delegate void KeyFrameLengthChangedHandler(Guid keyFrameId, int startFrame, int duration);
 public abstract class KeyFrame : IReadOnlyKeyFrame
 {
     private int startFrame;
     private int duration;
     private bool isVisible = true;
     
+    public event KeyFrameVisibilityChangedHandler? KeyFrameVisibilityChanged;
+    public event KeyFrameLengthChangedHandler? KeyFrameLengthChanged;
+    
     public virtual int StartFrame
     {
         get => startFrame;
@@ -21,7 +26,7 @@ public abstract class KeyFrame : IReadOnlyKeyFrame
             }
 
             startFrame = value;
-            TargetNode.SetKeyFrameLength(Id, startFrame, Duration);
+            KeyFrameLengthChanged?.Invoke(Id, StartFrame, Duration);
         }
     }
 
@@ -36,7 +41,7 @@ public abstract class KeyFrame : IReadOnlyKeyFrame
             }
 
             duration = value;
-            TargetNode.SetKeyFrameLength(Id, StartFrame, Duration);
+            KeyFrameLengthChanged?.Invoke(Id, StartFrame, Duration);
         }
     }
     
@@ -51,22 +56,23 @@ public abstract class KeyFrame : IReadOnlyKeyFrame
         set
         {
             isVisible = value;
-            TargetNode.SetKeyFrameVisibility(Id, isVisible);
+            KeyFrameVisibilityChanged?.Invoke(Id, IsVisible);
         }
     }
 
-    public Node TargetNode { get; }
-    
-    IReadOnlyNode IReadOnlyKeyFrame.TargetNode => TargetNode;
-
-    protected KeyFrame(Node node, int startFrame)
+    protected KeyFrame(Guid targetNode, int startFrame)
     {
-        TargetNode = node;
-        NodeId = node.Id;
+        NodeId = targetNode;
         this.startFrame = startFrame;
         duration = 1;
         Id = Guid.NewGuid();
     }
     
     public abstract KeyFrame Clone();
+
+    public void ClearEvents()
+    {
+        KeyFrameVisibilityChanged = null;
+        KeyFrameLengthChanged = null;
+    }
 }

+ 16 - 8
src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterKeyFrame.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Animations;
@@ -7,20 +8,27 @@ internal class RasterKeyFrame : KeyFrame, IReadOnlyRasterKeyFrame
 {
     public Document Document { get; set; }
 
-    private ImageLayerNode targetNode;
 
-    public RasterKeyFrame(Guid id, ImageLayerNode node, int startFrame, Document document)
-        : base(node, startFrame)
+    public RasterKeyFrame(Guid keyFrameId, Guid nodeId, int startFrame, Document document)
+        : base(nodeId, startFrame)
     {
-        Id = id;
-        targetNode = node;
+        Id = keyFrameId;
         Document = document;
     }
 
     public override KeyFrame Clone()
     {
-        return new RasterKeyFrame(Id, targetNode, StartFrame, Document) { Duration = Duration, IsVisible = IsVisible };
+        return new RasterKeyFrame(Id, NodeId, StartFrame, Document) { Duration = Duration, IsVisible = IsVisible };
     }
 
-    public IReadOnlyChunkyImage Image => targetNode.GetLayerImageByKeyFrameGuid(Id);
+    public IReadOnlyChunkyImage GetTargetImage(IReadOnlyCollection<IReadOnlyNode> allNodes)
+    {
+        IReadOnlyNode owner = allNodes.First(x => x.Id == NodeId);
+        if(owner is ImageLayerNode imageLayer)
+        {
+            return imageLayer.GetLayerImageByKeyFrameGuid(Id);
+        }
+
+        throw new InvalidOperationException("Node is not an image layer");
+    }
 }

+ 6 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ImageLayerNode.cs

@@ -320,15 +320,14 @@ public class ImageLayerNode : LayerNode, IReadOnlyImageNode
 
     public override Node CreateCopy()
     {
-        return new ImageLayerNode(size)
+        var image = new ImageLayerNode(size)
         {
-            MemberName = MemberName,
-            keyFrames = new List<KeyFrameData>()
-            {
-                // we are only copying the layer image, keyframes probably shouldn't be copied since they are controlled by AnimationData
-                new KeyFrameData(Guid.NewGuid(), 0, 0, ImageLayerKey) { Data = layerImage.CloneFromCommitted() }
-            }
+            MemberName = this.MemberName,
         };
+        
+        image.keyFrames.Clear();
+        
+        return image;
     }
 
     public override void Dispose()

+ 0 - 1
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyKeyFrame.cs

@@ -9,5 +9,4 @@ public interface IReadOnlyKeyFrame
     public Guid NodeId { get; }
     public Guid Id { get; }
     public bool IsVisible { get; }
-    public IReadOnlyNode TargetNode { get; }
 }

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyRasterKeyFrame.cs

@@ -1,6 +1,8 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 public interface IReadOnlyRasterKeyFrame : IReadOnlyKeyFrame
 {
-    IReadOnlyChunkyImage Image { get; }
+    IReadOnlyChunkyImage GetTargetImage(IReadOnlyCollection<IReadOnlyNode> allNodes);
 }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Animation/CreateRasterKeyFrame_Change.cs

@@ -42,7 +42,7 @@ internal class CreateRasterKeyFrame_Change : Change
         ChunkyImage img = cloneFromImage?.CloneFromCommitted() ?? new ChunkyImage(target.Size);
 
         var keyFrame =
-            new RasterKeyFrame(createdKeyFrameId, targetNode, _frame, target);
+            new RasterKeyFrame(createdKeyFrameId, targetNode.Id, _frame, target);
 
         var existingData = targetNode.KeyFrames.FirstOrDefault(x => x.KeyFrameGuid == createdKeyFrameId);
 

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/DeleteNode_Change.cs

@@ -40,7 +40,7 @@ internal class DeleteNode_Change : Change
 
     public static GroupKeyFrame? CloneGroupKeyFrame(Document target, Guid id)
     {
-        GroupKeyFrame group = target.AnimationData.KeyFrames.FirstOrDefault(x => x.TargetNode.Id == id) as GroupKeyFrame;
+        GroupKeyFrame group = target.AnimationData.KeyFrames.FirstOrDefault(x => x.NodeId == id) as GroupKeyFrame;
         if (group is null)
             return null;
         return group.Clone() as GroupKeyFrame;
@@ -91,7 +91,8 @@ internal class DeleteNode_Change : Change
     {
         if (savedKeyFrameGroup != null)
         {
-            doc.AnimationData.AddKeyFrame(savedKeyFrameGroup.Clone());
+            var cloned = savedKeyFrameGroup.Clone();
+            doc.AnimationData.AddKeyFrame(cloned);
             foreach (var keyFrame in savedKeyFrameGroup.Children)
             {
                 changes.Add(new CreateRasterKeyFrame_ChangeInfo(keyFrame.NodeId, keyFrame.StartFrame, keyFrame.Id, false));

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

@@ -54,7 +54,7 @@ internal partial class DocumentViewModel
             PreviewImage =
                 (TryRenderWholeImage(0).Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
             ReferenceLayer = GetReferenceLayer(doc, serializationConfig),
-            AnimationData = ToAnimationData(doc.AnimationData, nodeIdMap, keyFrameIdMap),
+            AnimationData = ToAnimationData(doc.AnimationData, doc.NodeGraph, nodeIdMap, keyFrameIdMap),
             ImageEncoderUsed = encoder.EncodedFormatName
         };
 
@@ -204,17 +204,18 @@ internal partial class DocumentViewModel
     private ColorCollection ToCollection(IList<PaletteColor> collection) =>
         new(collection.Select(x => Color.FromArgb(255, x.R, x.G, x.B)));
 
-    private AnimationData ToAnimationData(IReadOnlyAnimationData animationData, Dictionary<Guid, int> nodeIdMap, Dictionary<Guid, int> keyFrameIds)
+    private AnimationData ToAnimationData(IReadOnlyAnimationData animationData, IReadOnlyNodeGraph graph, Dictionary<Guid, int> nodeIdMap, Dictionary<Guid, int> keyFrameIds)
     {
         var animData = new AnimationData();
         animData.KeyFrameGroups = new List<KeyFrameGroup>();
         animData.FrameRate = animationData.FrameRate;
-        BuildKeyFrames(animationData.KeyFrames, animData, nodeIdMap, keyFrameIds);
+        BuildKeyFrames(animationData.KeyFrames, animData, graph, nodeIdMap, keyFrameIds);
 
         return animData;
     }
 
     private static void BuildKeyFrames(IReadOnlyList<IReadOnlyKeyFrame> root, AnimationData animationData,
+        IReadOnlyNodeGraph graph,
         Dictionary<Guid, int> nodeIdMap, Dictionary<Guid, int> keyFrameIds)
     {
         foreach (var keyFrame in root)
@@ -229,7 +230,7 @@ internal partial class DocumentViewModel
                 {
                     if (child is IReadOnlyRasterKeyFrame rasterKeyFrame)
                     {
-                        BuildRasterKeyFrame(rasterKeyFrame, group, nodeIdMap, keyFrameIds);
+                        BuildRasterKeyFrame(rasterKeyFrame, graph, group, nodeIdMap, keyFrameIds);
                     }
                 }
 
@@ -238,10 +239,11 @@ internal partial class DocumentViewModel
         }
     }
 
-    private static void BuildRasterKeyFrame(IReadOnlyRasterKeyFrame rasterKeyFrame, KeyFrameGroup group,
+    private static void BuildRasterKeyFrame(IReadOnlyRasterKeyFrame rasterKeyFrame, IReadOnlyNodeGraph graph, KeyFrameGroup group,
         Dictionary<Guid, int> idMap, Dictionary<Guid, int> keyFrameIds)
     {
-        var bounds = rasterKeyFrame.Image.FindChunkAlignedMostUpToDateBounds();
+        IReadOnlyChunkyImage image = rasterKeyFrame.GetTargetImage(graph.AllNodes);
+        var bounds = image.FindChunkAlignedMostUpToDateBounds();
 
         DrawingSurface surface = null;
 
@@ -250,7 +252,7 @@ internal partial class DocumentViewModel
             surface = DrawingBackendApi.Current.SurfaceImplementation.Create(
                 new ImageInfo(bounds.Value.Width, bounds.Value.Height));
 
-            rasterKeyFrame.Image.DrawMostUpToDateRegionOn(
+            image.DrawMostUpToDateRegionOn(
                 new RectI(0, 0, bounds.Value.Width, bounds.Value.Height), ChunkResolution.Full, surface,
                 new VecI(0, 0));
         }