Browse Source

Support for framerate in pixiparser

flabbet 1 year ago
parent
commit
4c7879ef2b

+ 25 - 5
src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs

@@ -25,7 +25,7 @@ internal class DocumentViewModelBuilder
     public List<PaletteColor> Palette { get; set; } = new List<PaletteColor>();
     public List<PaletteColor> Palette { get; set; } = new List<PaletteColor>();
 
 
     public ReferenceLayerBuilder ReferenceLayer { get; set; }
     public ReferenceLayerBuilder ReferenceLayer { get; set; }
-    public List<KeyFrameBuilder> AnimationData { get; set; } = new List<KeyFrameBuilder>();
+    public AnimationDataBuilder AnimationData { get; set; }
 
 
     public NodeGraphBuilder Graph { get; set; }
     public NodeGraphBuilder Graph { get; set; }
     public string ImageEncoderUsed { get; set; } = "QOI";
     public string ImageEncoderUsed { get; set; } = "QOI";
@@ -83,11 +83,12 @@ internal class DocumentViewModelBuilder
 
 
     public DocumentViewModelBuilder WithAnimationData(AnimationData? animationData)
     public DocumentViewModelBuilder WithAnimationData(AnimationData? animationData)
     {
     {
-        AnimationData = new List<KeyFrameBuilder>();
+        AnimationData = new AnimationDataBuilder();
 
 
         if (animationData != null && animationData.KeyFrameGroups.Count > 0)
         if (animationData != null && animationData.KeyFrameGroups.Count > 0)
         {
         {
-            BuildKeyFrames(animationData.KeyFrameGroups.ToList(), AnimationData);
+            AnimationData.WithFrameRate(animationData.FrameRate);
+            BuildKeyFrames(animationData.KeyFrameGroups.ToList(), AnimationData.KeyFrameGroups);
         }
         }
 
 
         return this;
         return this;
@@ -189,6 +190,24 @@ internal class DocumentViewModelBuilder
     }
     }
 }
 }
 
 
+internal class AnimationDataBuilder
+{
+    public int FrameRate { get; set; } = 24;
+    public List<KeyFrameBuilder> KeyFrameGroups { get; set; } = new List<KeyFrameBuilder>();
+
+    public AnimationDataBuilder WithFrameRate(int frameRate)
+    {
+        FrameRate = frameRate;
+        return this;
+    }
+
+    public AnimationDataBuilder WithKeyFrameGroups(Action<List<KeyFrameBuilder>> builder)
+    {
+        builder(KeyFrameGroups);
+        return this;
+    }
+}
+
 internal class KeyFrameBuilder()
 internal class KeyFrameBuilder()
 {
 {
     public int NodeId { get; set; }
     public int NodeId { get; set; }
@@ -338,8 +357,9 @@ internal class NodeGraphBuilder
                 {
                 {
                     InputConnections.Add(connection.OutputNodeId, new List<(string, string)>());
                     InputConnections.Add(connection.OutputNodeId, new List<(string, string)>());
                 }
                 }
-                
-                InputConnections[connection.OutputNodeId].Add((connection.InputPropertyName, connection.OutputPropertyName)); 
+
+                InputConnections[connection.OutputNodeId]
+                    .Add((connection.InputPropertyName, connection.OutputPropertyName));
             }
             }
 
 
             return this;
             return this;

+ 8 - 0
src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs

@@ -193,6 +193,9 @@ internal class DocumentUpdater
             case NodeName_ChangeInfo info:
             case NodeName_ChangeInfo info:
                 ProcessNodeName(info);
                 ProcessNodeName(info);
                 break;
                 break;
+            case FrameRate_ChangeInfo info:
+                ProcessFrameRate(info);
+                break;
         }
         }
     }
     }
 
 
@@ -598,4 +601,9 @@ internal class DocumentUpdater
         NodeViewModel node = doc.StructureHelper.FindNode<NodeViewModel>(info.NodeId);
         NodeViewModel node = doc.StructureHelper.FindNode<NodeViewModel>(info.NodeId);
         node.SetName(info.NewName);
         node.SetName(info.NewName);
     }
     }
+    
+    private void ProcessFrameRate(FrameRate_ChangeInfo info)
+    {
+        doc.AnimationHandler.SetFrameRate(info.NewFrameRate);
+    }
 }
 }

+ 1 - 0
src/PixiEditor.AvaloniaUI/Models/Handlers/IAnimationHandler.cs

@@ -8,6 +8,7 @@ internal interface IAnimationHandler
     public int ActiveFrameBindable { get; set; }
     public int ActiveFrameBindable { get; set; }
     public KeyFrameTime ActiveFrameTime { get; }
     public KeyFrameTime ActiveFrameTime { get; }
     public void CreateRasterKeyFrame(Guid targetLayerGuid, int frame, Guid? toCloneFrom = null, int? frameToCopyFrom = null);
     public void CreateRasterKeyFrame(Guid targetLayerGuid, int frame, Guid? toCloneFrom = null, int? frameToCopyFrom = null);
+    public void SetFrameRate(int newFrameRate);
     public void SetActiveFrame(int newFrame);
     public void SetActiveFrame(int newFrame);
     public void SetFrameLength(Guid keyFrameId, int newStartFrame, int newDuration);
     public void SetFrameLength(Guid keyFrameId, int newStartFrame, int newDuration);
     public void SetKeyFrameVisibility(Guid infoKeyFrameId, bool infoIsVisible);
     public void SetKeyFrameVisibility(Guid infoKeyFrameId, bool infoIsVisible);

+ 15 - 7
src/PixiEditor.AvaloniaUI/ViewModels/Document/AnimationDataViewModel.cs

@@ -14,7 +14,7 @@ namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 {
 {
     private int _activeFrameBindable = 1;
     private int _activeFrameBindable = 1;
-    private int _frameRate = 60;
+    private int frameRateBindable = 60;
     public DocumentViewModel Document { get; }
     public DocumentViewModel Document { get; }
     protected DocumentInternalParts Internals { get; }
     protected DocumentInternalParts Internals { get; }
     public IReadOnlyCollection<IKeyFrameHandler> KeyFrames => keyFrames;
     public IReadOnlyCollection<IKeyFrameHandler> KeyFrames => keyFrames;
@@ -38,14 +38,15 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
 
 
     public IAnimationRenderer Renderer { get; set; }
     public IAnimationRenderer Renderer { get; set; }
 
 
-    public int FrameRate
+    public int FrameRateBindable
     {
     {
-        get => _frameRate;
+        get => frameRateBindable;
         set
         set
         {
         {
-            _frameRate = value;
-            OnPropertyChanged(nameof(FrameRate));
-            OnPropertyChanged(nameof(DefaultEndFrame));
+            if (Document.UpdateableChangeActive)
+                return;
+
+            Internals.ActionAccumulator.AddFinishedActions(new SetFrameRate_Action(value)); 
         }
         }
     }
     }
 
 
@@ -56,7 +57,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
     
     
     private double ActiveNormalizedTime => (double)(ActiveFrameBindable - FirstFrame) / FramesCount;
     private double ActiveNormalizedTime => (double)(ActiveFrameBindable - FirstFrame) / FramesCount;
 
 
-    private int DefaultEndFrame => FrameRate; // 1 second
+    private int DefaultEndFrame => FrameRateBindable; // 1 second
 
 
     public AnimationDataViewModel(DocumentViewModel document, DocumentInternalParts internals)
     public AnimationDataViewModel(DocumentViewModel document, DocumentInternalParts internals)
     {
     {
@@ -110,6 +111,13 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         }
         }
     }
     }
     
     
+    public void SetFrameRate(int newFrameRate)
+    {
+        frameRateBindable = newFrameRate;
+        OnPropertyChanged(nameof(FrameRateBindable));
+        OnPropertyChanged(nameof(DefaultEndFrame));
+    }
+    
     public void SetActiveFrame(int newFrame)
     public void SetActiveFrame(int newFrame)
     {
     {
         _activeFrameBindable = newFrame;
         _activeFrameBindable = newFrame;

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

@@ -215,6 +215,7 @@ internal partial class DocumentViewModel
     {
     {
         var animData = new AnimationData();
         var animData = new AnimationData();
         animData.KeyFrameGroups = new List<KeyFrameGroup>();
         animData.KeyFrameGroups = new List<KeyFrameGroup>();
+        animData.FrameRate = animationData.FrameRate;
         BuildKeyFrames(animationData.KeyFrames, animData, nodeIdMap, keyFrameIds);
         BuildKeyFrames(animationData.KeyFrames, animData, nodeIdMap, keyFrameIds);
 
 
         return animData;
         return animData;

+ 16 - 14
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -345,7 +345,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             Guid guid = Guid.NewGuid();
             Guid guid = Guid.NewGuid();
             mappedNodeIds.Add(id, guid);
             mappedNodeIds.Add(id, guid);
             acc.AddActions(new CreateNodeFromName_Action(serializedNode.UniqueNodeName, guid));
             acc.AddActions(new CreateNodeFromName_Action(serializedNode.UniqueNodeName, guid));
-            acc.AddFinishedActions(new NodePosition_Action(guid, serializedNode.Position.ToVecD()), new EndNodePosition_Action());
+            acc.AddFinishedActions(new NodePosition_Action(guid, serializedNode.Position.ToVecD()),
+                new EndNodePosition_Action());
 
 
             if (serializedNode.InputValues != null)
             if (serializedNode.InputValues != null)
             {
             {
@@ -453,24 +454,25 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             }
             }
         }*/
         }*/
 
 
-        void AddAnimationData(List<KeyFrameBuilder> data, Dictionary<int, Guid> mappedIds,
+        void AddAnimationData(AnimationDataBuilder data, Dictionary<int, Guid> mappedIds,
             Dictionary<int, Guid> mappedKeyFrameIds)
             Dictionary<int, Guid> mappedKeyFrameIds)
         {
         {
-            foreach (var keyFrame in data)
+            acc.AddActions(new SetFrameRate_Action(data.FrameRate));
+            foreach (var keyFrame in data.KeyFrameGroups)
             {
             {
-                if (keyFrame is GroupKeyFrameBuilder groupKeyFrameBuilder)
+                if (keyFrame is GroupKeyFrameBuilder group)
                 {
                 {
-                    AddAnimationData(groupKeyFrameBuilder.Children, mappedIds, mappedKeyFrameIds);
-                }
-                else
-                {
-                    acc.AddActions(
-                        new CreateRasterKeyFrame_Action(
-                            mappedIds[keyFrame.NodeId],
-                            mappedKeyFrameIds[keyFrame.KeyFrameId],
-                            -1, -1, default));
+                    foreach (var child in group.Children)
+                    {
+                        acc.AddActions(
+                            new CreateRasterKeyFrame_Action(
+                                mappedIds[child.NodeId],
+                                mappedKeyFrameIds[child.KeyFrameId],
+                                -1, -1, default));
+
+                        acc.AddFinishedActions();
+                    }
 
 
-                    acc.AddFinishedActions();
                 }
                 }
             }
             }
         }
         }

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Dialogs/ExportFileDialog.cs

@@ -110,7 +110,7 @@ internal class ExportFileDialog : CustomDialog
             {
             {
                 Size = new VecI(FileWidth, FileHeight),
                 Size = new VecI(FileWidth, FileHeight),
                 OutputFormat = ChosenFormat.PrimaryExtension.Replace(".", ""),
                 OutputFormat = ChosenFormat.PrimaryExtension.Replace(".", ""),
-                FrameRate = document.AnimationDataViewModel.FrameRate
+                FrameRate = document.AnimationDataViewModel.FrameRateBindable
             }
             }
             : null;
             : null;
             ExportConfig.ExportAsSpriteSheet = popup.IsSpriteSheetExport;
             ExportConfig.ExportAsSpriteSheet = popup.IsSpriteSheetExport;

+ 2 - 2
src/PixiEditor.AvaloniaUI/Views/Dialogs/ExportFilePopup.axaml.cs

@@ -173,7 +173,7 @@ internal partial class ExportFilePopup : PixiEditorPopup
         this.document = document;
         this.document = document;
         videoPreviewTimer = new DispatcherTimer(DispatcherPriority.Normal)
         videoPreviewTimer = new DispatcherTimer(DispatcherPriority.Normal)
         {
         {
-            Interval = TimeSpan.FromMilliseconds(1000f / document.AnimationDataViewModel.FrameRate)
+            Interval = TimeSpan.FromMilliseconds(1000f / document.AnimationDataViewModel.FrameRateBindable)
         };
         };
         videoPreviewTimer.Tick += OnVideoPreviewTimerOnTick;
         videoPreviewTimer.Tick += OnVideoPreviewTimerOnTick;
 
 
@@ -235,7 +235,7 @@ internal partial class ExportFilePopup : PixiEditorPopup
         if (IsVideoExport)
         if (IsVideoExport)
         {
         {
             StartRenderAnimationJob();
             StartRenderAnimationJob();
-            videoPreviewTimer.Interval = TimeSpan.FromMilliseconds(1000f / document.AnimationDataViewModel.FrameRate);
+            videoPreviewTimer.Interval = TimeSpan.FromMilliseconds(1000f / document.AnimationDataViewModel.FrameRateBindable);
         }
         }
         else
         else
         {
         {

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Dock/TimelineDockView.axaml

@@ -16,7 +16,7 @@
         KeyFrames="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.KeyFrames}" 
         KeyFrames="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.KeyFrames}" 
         ActiveFrame="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.ActiveFrameBindable, Mode=TwoWay}"
         ActiveFrame="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.ActiveFrameBindable, Mode=TwoWay}"
         NewKeyFrameCommand="{xaml:Command PixiEditor.Animation.CreateRasterKeyFrame}"
         NewKeyFrameCommand="{xaml:Command PixiEditor.Animation.CreateRasterKeyFrame}"
-        Fps="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.FrameRate, Mode=TwoWay}"
+        Fps="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.FrameRateBindable, Mode=TwoWay}"
         DefaultEndFrame="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.DefaultEndFrame}"
         DefaultEndFrame="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.DefaultEndFrame}"
         DuplicateKeyFrameCommand="{xaml:Command PixiEditor.Animation.DuplicateRasterKeyFrame}"
         DuplicateKeyFrameCommand="{xaml:Command PixiEditor.Animation.DuplicateRasterKeyFrame}"
         DeleteKeyFrameCommand="{xaml:Command PixiEditor.Animation.DeleteKeyFrames, UseProvided=True}"
         DeleteKeyFrameCommand="{xaml:Command PixiEditor.Animation.DeleteKeyFrames, UseProvided=True}"

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Animation/FrameRate_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Animation;
+
+public record FrameRate_ChangeInfo(int NewFrameRate) : IChangeInfo;

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Animations/AnimationData.cs

@@ -5,6 +5,7 @@ namespace PixiEditor.ChangeableDocument.Changeables.Animations;
 
 
 internal class AnimationData : IReadOnlyAnimationData
 internal class AnimationData : IReadOnlyAnimationData
 {
 {
+    public int FrameRate { get; set; } = 24;
     public IReadOnlyList<IReadOnlyKeyFrame> KeyFrames => keyFrames;
     public IReadOnlyList<IReadOnlyKeyFrame> KeyFrames => keyFrames;
 
 
     private List<KeyFrame> keyFrames = new List<KeyFrame>();
     private List<KeyFrame> keyFrames = new List<KeyFrame>();

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

@@ -2,6 +2,7 @@
 
 
 public interface IReadOnlyAnimationData
 public interface IReadOnlyAnimationData
 {
 {
+    public int FrameRate { get; }
     public IReadOnlyList<IReadOnlyKeyFrame> KeyFrames { get; }
     public IReadOnlyList<IReadOnlyKeyFrame> KeyFrames { get; }
     public bool TryFindKeyFrame<T>(Guid id, out T keyFrame) where T : IReadOnlyKeyFrame;
     public bool TryFindKeyFrame<T>(Guid id, out T keyFrame) where T : IReadOnlyKeyFrame;
 }
 }

+ 3 - 0
src/PixiEditor.ChangeableDocument/Changes/Animation/CreateRasterKeyFrame_Change.cs

@@ -47,6 +47,7 @@ internal class CreateRasterKeyFrame_Change : Change
         var existingData = targetNode.KeyFrames.FirstOrDefault(x => x.KeyFrameGuid == createdKeyFrameId);
         var existingData = targetNode.KeyFrames.FirstOrDefault(x => x.KeyFrameGuid == createdKeyFrameId);
 
 
         bool isVisible = true;
         bool isVisible = true;
+        int duration = 1;
 
 
         if (existingData is null)
         if (existingData is null)
         {
         {
@@ -56,6 +57,7 @@ internal class CreateRasterKeyFrame_Change : Change
         else
         else
         {
         {
             _frame = existingData.StartFrame;
             _frame = existingData.StartFrame;
+            duration = existingData.Duration;
 
 
             isVisible = existingData.IsVisible;
             isVisible = existingData.IsVisible;
         }
         }
@@ -66,6 +68,7 @@ internal class CreateRasterKeyFrame_Change : Change
         List<IChangeInfo> infos = new()
         List<IChangeInfo> infos = new()
         {
         {
             new CreateRasterKeyFrame_ChangeInfo(_targetLayerGuid, _frame, createdKeyFrameId, cloneFrom.HasValue),
             new CreateRasterKeyFrame_ChangeInfo(_targetLayerGuid, _frame, createdKeyFrameId, cloneFrom.HasValue),
+            new KeyFrameLength_ChangeInfo(createdKeyFrameId, _frame, duration),
             new KeyFrameVisibility_ChangeInfo(_targetLayerGuid, isVisible)
             new KeyFrameVisibility_ChangeInfo(_targetLayerGuid, isVisible)
         };
         };
 
 

+ 37 - 0
src/PixiEditor.ChangeableDocument/Changes/Animation/SetFrameRate_Change.cs

@@ -0,0 +1,37 @@
+using PixiEditor.ChangeableDocument.ChangeInfos.Animation;
+
+namespace PixiEditor.ChangeableDocument.Changes.Animation;
+
+internal class SetFrameRate_Change : Change
+{
+    private int frameRate;
+    private int oldFrameRate;
+    
+    [GenerateMakeChangeAction]
+    public SetFrameRate_Change(int frameRate)
+    {
+        this.frameRate = frameRate;
+    }
+    
+    public override bool InitializeAndValidate(Document target)
+    {
+        oldFrameRate = target.AnimationData.FrameRate;
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        target.AnimationData.FrameRate = frameRate;
+        
+        ignoreInUndo = false;
+
+        return new FrameRate_ChangeInfo(frameRate);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.AnimationData.FrameRate = oldFrameRate;
+
+        return new FrameRate_ChangeInfo(oldFrameRate);
+    }
+}