Browse Source

Added animation shortcuts

flabbet 1 year ago
parent
commit
a3ce662800

+ 4 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -724,5 +724,8 @@
   "ISLAND_EXAMPLE": "Islands",
   "ISLAND_EXAMPLE": "Islands",
   "ONION_FRAMES_COUNT": "Onion frames",
   "ONION_FRAMES_COUNT": "Onion frames",
   "ONION_OPACITY": "Onion opacity",
   "ONION_OPACITY": "Onion opacity",
-  "TOGGLE_ONION_SKINNING": "Toggle onion skinning"
+  "TOGGLE_ONION_SKINNING": "Toggle onion skinning",
+  "CHANGE_ACTIVE_FRAME_PREVIOUS": "Change active frame to previous",
+  "CHANGE_ACTIVE_FRAME_NEXT": "Change active frame to next",
+  "TOGGLE_ANIMATION": "Toggle animation"
 }
 }

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

@@ -202,6 +202,9 @@ internal class DocumentUpdater
             case OnionFrames_ChangeInfo info:
             case OnionFrames_ChangeInfo info:
                 ProcessSetOnionFrames(info);
                 ProcessSetOnionFrames(info);
                 break;
                 break;
+            case SetPlayingState_PassthroughAction info:
+                ProcessPlayAnimation(info);
+                break;
         }
         }
     }
     }
 
 
@@ -465,6 +468,11 @@ internal class DocumentUpdater
         doc.AnimationHandler.SetOnionSkinning(info.IsOnionSkinningEnabled);
         doc.AnimationHandler.SetOnionSkinning(info.IsOnionSkinningEnabled);
     }
     }
     
     
+    private void ProcessPlayAnimation(SetPlayingState_PassthroughAction info)
+    {
+        doc.AnimationHandler.SetPlayingState(info.Play);
+    }
+    
     private void ProcessCreateRasterKeyFrame(CreateRasterKeyFrame_ChangeInfo info)
     private void ProcessCreateRasterKeyFrame(CreateRasterKeyFrame_ChangeInfo info)
     {
     {
         doc.AnimationHandler.AddKeyFrame(new RasterKeyFrameViewModel(info.TargetLayerGuid, info.Frame, 1, info.KeyFrameId, 
         doc.AnimationHandler.AddKeyFrame(new RasterKeyFrameViewModel(info.TargetLayerGuid, info.Frame, 1, info.KeyFrameId, 

+ 6 - 0
src/PixiEditor/Models/DocumentPassthroughActions/SetPlayingState_PassthroughAction.cs

@@ -0,0 +1,6 @@
+using PixiEditor.ChangeableDocument.Actions;
+using PixiEditor.ChangeableDocument.ChangeInfos;
+
+namespace PixiEditor.Models.DocumentPassthroughActions;
+
+public record SetPlayingState_PassthroughAction(bool Play) : IAction, IChangeInfo;

+ 2 - 0
src/PixiEditor/Models/Handlers/IAnimationHandler.cs

@@ -10,6 +10,7 @@ internal interface IAnimationHandler
     public bool OnionSkinningEnabledBindable { get; set; }
     public bool OnionSkinningEnabledBindable { get; set; }
     public int OnionFramesBindable { get; set; }
     public int OnionFramesBindable { get; set; }
     public double OnionOpacityBindable { get; set; }
     public double OnionOpacityBindable { get; set; }
+    public bool IsPlayingBindable { get; set; }
     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 SetFrameRate(int newFrameRate);
     public void SetActiveFrame(int newFrame);
     public void SetActiveFrame(int newFrame);
@@ -25,4 +26,5 @@ internal interface IAnimationHandler
     public int FirstFrame { get; }
     public int FirstFrame { get; }
     public int LastFrame { get; }
     public int LastFrame { get; }
     public void SetOnionFrames(int frames, double opacity);
     public void SetOnionFrames(int frames, double opacity);
+    public void SetPlayingState(bool play);
 }
 }

+ 1 - 1
src/PixiEditor/Styles/Templates/Timeline.axaml

@@ -77,7 +77,7 @@
                     <Border Grid.Row="0" Grid.Column="1">
                     <Border Grid.Row="0" Grid.Column="1">
                         <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="5">
                         <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="5">
                             <ToggleButton Margin="0, 5" Width="24" HorizontalAlignment="Center" Classes="PlayButton"
                             <ToggleButton Margin="0, 5" Width="24" HorizontalAlignment="Center" Classes="PlayButton"
-                                          Name="PART_PlayToggle" />
+                                          Name="PART_PlayToggle" IsChecked="{Binding IsPlaying, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
                             <TextBlock VerticalAlignment="Center" FontSize="14">
                             <TextBlock VerticalAlignment="Center" FontSize="14">
                                 <Run>
                                 <Run>
                                     <Run.Text>
                                     <Run.Text>

+ 19 - 0
src/PixiEditor/ViewModels/Document/AnimationDataViewModel.cs

@@ -27,6 +27,7 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
     private KeyFrameCollection keyFrames = new KeyFrameCollection();
     private KeyFrameCollection keyFrames = new KeyFrameCollection();
     private List<IKeyFrameHandler> allKeyFrames = new List<IKeyFrameHandler>();
     private List<IKeyFrameHandler> allKeyFrames = new List<IKeyFrameHandler>();
     private bool onionSkinningEnabled;
     private bool onionSkinningEnabled;
+    private bool isPlayingBindable;
 
 
     public int ActiveFrameBindable
     public int ActiveFrameBindable
     {
     {
@@ -89,6 +90,18 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             Internals.ActionAccumulator.AddFinishedActions(new SetOnionSettings_Action(OnionFramesBindable, value)); 
             Internals.ActionAccumulator.AddFinishedActions(new SetOnionSettings_Action(OnionFramesBindable, value)); 
         }
         }
     }
     }
+    
+    public bool IsPlayingBindable
+    {
+        get => isPlayingBindable;
+        set
+        {
+            if (Document.UpdateableChangeActive)
+                return;
+
+            Internals.ActionAccumulator.AddFinishedActions(new SetPlayingState_PassthroughAction(value));
+        }
+    }
 
 
     public int FirstFrame => keyFrames.Count > 0 ? keyFrames.Min(x => x.StartFrameBindable) : 0;
     public int FirstFrame => keyFrames.Count > 0 ? keyFrames.Min(x => x.StartFrameBindable) : 0;
 
 
@@ -177,6 +190,12 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         OnPropertyChanged(nameof(ActiveFrameBindable));
         OnPropertyChanged(nameof(ActiveFrameBindable));
     }
     }
     
     
+    public void SetPlayingState(bool value)
+    {
+        isPlayingBindable = value;
+        OnPropertyChanged(nameof(IsPlayingBindable));
+    }
+    
     public void SetOnionSkinning(bool value)
     public void SetOnionSkinning(bool value)
     {
     {
         onionSkinningEnabled = value;
         onionSkinningEnabled = value;

+ 55 - 24
src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -16,9 +16,39 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     public AnimationsViewModel(ViewModelMain owner) : base(owner)
     public AnimationsViewModel(ViewModelMain owner) : base(owner)
     {
     {
     }
     }
-    
-    [Command.Basic("PixiEditor.Animation.CreateRasterKeyFrame", "Create Raster Key Frame", "Create a raster key frame", Parameter = false, AnalyticsTrack = true)]
-    [Command.Basic("PixiEditor.Animation.DuplicateRasterKeyFrame", "Duplicate Raster Key Frame", "Duplicate a raster key frame", Parameter = true, AnalyticsTrack = true)]
+
+    [Command.Basic("PixiEditor.Animation.NudgeActiveFrameNext", "CHANGE_ACTIVE_FRAME_NEXT",
+        "CHANGE_ACTIVE_FRAME_NEXT",
+        Parameter = 1, Key = Key.Right)]
+    [Command.Basic("PixiEditor.Animation.NudgeActiveFramePrevious", "CHANGE_ACTIVE_FRAME_PREVIOUS",
+        "CHANGE_ACTIVE_FRAME_PREVIOUS",
+        Parameter = -1, Key = Key.Left)]
+    public void ChangeActiveFrame(int nudgeBy)
+    {
+        var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (activeDocument is null || activeDocument.TransformViewModel.TransformActive)
+            return;
+
+        int newFrame = activeDocument.AnimationDataViewModel.ActiveFrameBindable + nudgeBy;
+        newFrame = Math.Max(1, newFrame);
+        activeDocument.Operations.SetActiveFrame(newFrame);
+    }
+
+    [Command.Basic("PixiEditor.Animation.TogglePlayAnimation", "TOGGLE_ANIMATION", "TOGGLE_ANIMATION",
+        Key = Key.Space, Modifiers = KeyModifiers.Shift)]
+    public void ToggleAnimation()
+    {
+        var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (activeDocument is null)
+            return;
+
+        activeDocument.AnimationDataViewModel.IsPlayingBindable = !activeDocument.AnimationDataViewModel.IsPlayingBindable;
+    }
+
+    [Command.Basic("PixiEditor.Animation.CreateRasterKeyFrame", "Create Raster Key Frame", "Create a raster key frame",
+        Parameter = false, AnalyticsTrack = true)]
+    [Command.Basic("PixiEditor.Animation.DuplicateRasterKeyFrame", "Duplicate Raster Key Frame",
+        "Duplicate a raster key frame", Parameter = true, AnalyticsTrack = true)]
     public void CreateRasterKeyFrame(bool duplicate)
     public void CreateRasterKeyFrame(bool duplicate)
     {
     {
         var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
@@ -28,16 +58,16 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
         }
         }
 
 
         int newFrame = GetActiveFrame(activeDocument, activeDocument.SelectedStructureMember.Id);
         int newFrame = GetActiveFrame(activeDocument, activeDocument.SelectedStructureMember.Id);
-       
+
         Guid toCloneFrom = duplicate ? activeDocument.SelectedStructureMember.Id : Guid.Empty;
         Guid toCloneFrom = duplicate ? activeDocument.SelectedStructureMember.Id : Guid.Empty;
         int frameToCopyFrom = duplicate ? activeDocument.AnimationDataViewModel.ActiveFrameBindable : -1;
         int frameToCopyFrom = duplicate ? activeDocument.AnimationDataViewModel.ActiveFrameBindable : -1;
 
 
         activeDocument.AnimationDataViewModel.CreateRasterKeyFrame(
         activeDocument.AnimationDataViewModel.CreateRasterKeyFrame(
             activeDocument.SelectedStructureMember.Id,
             activeDocument.SelectedStructureMember.Id,
             newFrame,
             newFrame,
-            toCloneFrom, 
+            toCloneFrom,
             frameToCopyFrom);
             frameToCopyFrom);
-        
+
         activeDocument.Operations.SetActiveFrame(newFrame);
         activeDocument.Operations.SetActiveFrame(newFrame);
 
 
         Analytics.SendCreateKeyframe(
         Analytics.SendCreateKeyframe(
@@ -55,10 +85,10 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     {
     {
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
             return;
             return;
-        
+
         Owner.DocumentManagerSubViewModel.ActiveDocument?.AnimationDataViewModel.ToggleOnionSkinning(value);
         Owner.DocumentManagerSubViewModel.ActiveDocument?.AnimationDataViewModel.ToggleOnionSkinning(value);
     }
     }
-    
+
     [Command.Basic("PixiEditor.Animation.DeleteKeyFrames", "DELETE_KEY_FRAMES", "DELETE_KEY_FRAMES_DESCRIPTIVE",
     [Command.Basic("PixiEditor.Animation.DeleteKeyFrames", "DELETE_KEY_FRAMES", "DELETE_KEY_FRAMES_DESCRIPTIVE",
         ShortcutContext = typeof(TimelineDockViewModel), Key = Key.Delete, AnalyticsTrack = true)]
         ShortcutContext = typeof(TimelineDockViewModel), Key = Key.Delete, AnalyticsTrack = true)]
     public void DeleteKeyFrames()
     public void DeleteKeyFrames()
@@ -68,18 +98,18 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
 
 
         if (activeDocument is null || selected.Length == 0)
         if (activeDocument is null || selected.Length == 0)
             return;
             return;
-        
+
         List<Guid> keyFrameIds = selected.Select(x => x.Id).ToList();
         List<Guid> keyFrameIds = selected.Select(x => x.Id).ToList();
-        
-        for(int i = 0; i < keyFrameIds.Count; i++)
+
+        for (int i = 0; i < keyFrameIds.Count; i++)
         {
         {
-            if(!activeDocument.AnimationDataViewModel.TryFindKeyFrame<KeyFrameViewModel>(keyFrameIds[i], out _))
+            if (!activeDocument.AnimationDataViewModel.TryFindKeyFrame<KeyFrameViewModel>(keyFrameIds[i], out _))
             {
             {
                 keyFrameIds.RemoveAt(i);
                 keyFrameIds.RemoveAt(i);
-                i--;   
+                i--;
             }
             }
         }
         }
-        
+
         activeDocument.AnimationDataViewModel.DeleteKeyFrames(keyFrameIds);
         activeDocument.AnimationDataViewModel.DeleteKeyFrames(keyFrameIds);
     }
     }
 
 
@@ -100,19 +130,20 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
             activeDocument.AnimationDataViewModel.EndKeyFramesStartPos();
             activeDocument.AnimationDataViewModel.EndKeyFramesStartPos();
         }
         }
     }
     }
-    
-    
+
+
     private static int GetActiveFrame(DocumentViewModel activeDocument, Guid targetLayer)
     private static int GetActiveFrame(DocumentViewModel activeDocument, Guid targetLayer)
     {
     {
         int active = activeDocument.AnimationDataViewModel.ActiveFrameBindable;
         int active = activeDocument.AnimationDataViewModel.ActiveFrameBindable;
-        if (activeDocument.AnimationDataViewModel.TryFindKeyFrame<KeyFrameGroupViewModel>(targetLayer, out KeyFrameGroupViewModel groupViewModel))
+        if (activeDocument.AnimationDataViewModel.TryFindKeyFrame<KeyFrameGroupViewModel>(targetLayer,
+                out KeyFrameGroupViewModel groupViewModel))
         {
         {
-            if(active == groupViewModel.StartFrameBindable + groupViewModel.DurationBindable - 1)
+            if (active == groupViewModel.StartFrameBindable + groupViewModel.DurationBindable - 1)
             {
             {
                 return groupViewModel.StartFrameBindable + groupViewModel.DurationBindable;
                 return groupViewModel.StartFrameBindable + groupViewModel.DurationBindable;
             }
             }
         }
         }
-        
+
         return active;
         return active;
     }
     }
 
 
@@ -124,13 +155,13 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
         // TODO: same as below
         // TODO: same as below
         //Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.StartChangeActiveFrame();
         //Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.StartChangeActiveFrame();
     }
     }
-    
+
     [Command.Internal("PixiEditor.Document.ChangeActiveFrame", CanExecute = "PixiEditor.HasDocument")]
     [Command.Internal("PixiEditor.Document.ChangeActiveFrame", CanExecute = "PixiEditor.HasDocument")]
     public void ChangeActiveFrame(double newActiveFrame)
     public void ChangeActiveFrame(double newActiveFrame)
     {
     {
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
             return;
             return;
-        
+
         int intNewActiveFrame = (int)newActiveFrame;
         int intNewActiveFrame = (int)newActiveFrame;
         // TODO: Check if this should be implemented
         // TODO: Check if this should be implemented
         //Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.ChangeActiveFrame(intNewActiveFrame);
         //Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.ChangeActiveFrame(intNewActiveFrame);
@@ -141,15 +172,15 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
     {
     {
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
         if (Owner.DocumentManagerSubViewModel.ActiveDocument is null)
             return;
             return;
-        
+
         //Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.EndChangeActiveFrame();
         //Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet.EndChangeActiveFrame();
     }
     }
-    
+
     [Command.Internal("PixiEditor.Animation.ActiveFrameSet")]
     [Command.Internal("PixiEditor.Animation.ActiveFrameSet")]
     public void ActiveFrameSet(double value)
     public void ActiveFrameSet(double value)
     {
     {
         var document = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var document = Owner.DocumentManagerSubViewModel.ActiveDocument;
-        
+
         if (document is null)
         if (document is null)
             return;
             return;
 
 

+ 5 - 1
src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs

@@ -146,7 +146,11 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
 
 
     private void ProcessShortcutDown(bool isRepeat, Key key, KeyModifiers argsModifiers)
     private void ProcessShortcutDown(bool isRepeat, Key key, KeyModifiers argsModifiers)
     {
     {
-        HandleTransientKey(key);
+        if (argsModifiers == KeyModifiers.None)
+        {
+            HandleTransientKey(key);
+        }
+
         if (isRepeat && Owner.ShortcutController.LastCommands != null &&
         if (isRepeat && Owner.ShortcutController.LastCommands != null &&
             Owner.ShortcutController.LastCommands.Any(x => x is Command.ToolCommand))
             Owner.ShortcutController.LastCommands.Any(x => x is Command.ToolCommand))
         {
         {

+ 8 - 4
src/PixiEditor/ViewModels/SubViewModels/SelectionViewModel.cs

@@ -63,10 +63,14 @@ internal class SelectionViewModel : SubViewModel<ViewModelMain>
         Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(false);
         Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(false);
     }
     }
 
 
-    [Command.Basic("PixiEditor.Selection.NudgeSelectedObjectLeft", "NUDGE_SELECTED_LEFT", "NUDGE_SELECTED_LEFT", Key = Key.Left, Parameter = new int[] { -1, 0 }, Icon = PixiPerfectIcons.ChevronLeft, CanExecute = "PixiEditor.Selection.CanNudgeSelectedObject")]
-    [Command.Basic("PixiEditor.Selection.NudgeSelectedObjectRight", "NUDGE_SELECTED_RIGHT", "NUDGE_SELECTED_RIGHT", Key = Key.Right, Parameter = new int[] { 1, 0 }, Icon = PixiPerfectIcons.ChevronRight, CanExecute = "PixiEditor.Selection.CanNudgeSelectedObject")]
-    [Command.Basic("PixiEditor.Selection.NudgeSelectedObjectUp", "NUDGE_SELECTED_UP", "NUDGE_SELECTED_UP", Key = Key.Up, Parameter = new int[] { 0, -1 }, Icon = PixiPerfectIcons.ChevronUp, CanExecute = "PixiEditor.Selection.CanNudgeSelectedObject")]
-    [Command.Basic("PixiEditor.Selection.NudgeSelectedObjectDown", "NUDGE_SELECTED_DOWN", "NUDGE_SELECTED_DOWN", Key = Key.Down, Parameter = new int[] { 0, 1 }, Icon = PixiPerfectIcons.ChevronDown, CanExecute = "PixiEditor.Selection.CanNudgeSelectedObject")]
+    [Command.Basic("PixiEditor.Selection.NudgeSelectedObjectLeft", "NUDGE_SELECTED_LEFT", "NUDGE_SELECTED_LEFT", Key = Key.Left, Parameter = new int[] { -1, 0 }, Icon = PixiPerfectIcons.ChevronLeft, CanExecute = "PixiEditor.Selection.CanNudgeSelectedObject",
+        ShortcutContext = typeof(ViewportWindowViewModel))]
+    [Command.Basic("PixiEditor.Selection.NudgeSelectedObjectRight", "NUDGE_SELECTED_RIGHT", "NUDGE_SELECTED_RIGHT", Key = Key.Right, Parameter = new int[] { 1, 0 }, Icon = PixiPerfectIcons.ChevronRight, CanExecute = "PixiEditor.Selection.CanNudgeSelectedObject",
+        ShortcutContext = typeof(ViewportWindowViewModel))]
+    [Command.Basic("PixiEditor.Selection.NudgeSelectedObjectUp", "NUDGE_SELECTED_UP", "NUDGE_SELECTED_UP", Key = Key.Up, Parameter = new int[] { 0, -1 }, Icon = PixiPerfectIcons.ChevronUp, CanExecute = "PixiEditor.Selection.CanNudgeSelectedObject",
+        ShortcutContext = typeof(ViewportWindowViewModel))]
+    [Command.Basic("PixiEditor.Selection.NudgeSelectedObjectDown", "NUDGE_SELECTED_DOWN", "NUDGE_SELECTED_DOWN", Key = Key.Down, Parameter = new int[] { 0, 1 }, Icon = PixiPerfectIcons.ChevronDown, CanExecute = "PixiEditor.Selection.CanNudgeSelectedObject",
+        ShortcutContext = typeof(ViewportWindowViewModel))]
     public void NudgeSelectedObject(int[] dist)
     public void NudgeSelectedObject(int[] dist)
     {
     {
         VecI distance = new(dist[0], dist[1]);
         VecI distance = new(dist[0], dist[1]);

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

@@ -21,6 +21,7 @@
         OnionSkinningEnabled="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionSkinningEnabledBindable, Mode=TwoWay}"
         OnionSkinningEnabled="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionSkinningEnabledBindable, Mode=TwoWay}"
         OnionFrames="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionFramesBindable, Mode=TwoWay}"
         OnionFrames="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionFramesBindable, Mode=TwoWay}"
         OnionOpacity="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionOpacityBindable, Mode=TwoWay}"
         OnionOpacity="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.OnionOpacityBindable, Mode=TwoWay}"
+        IsPlaying="{Binding DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.IsPlayingBindable, Mode=TwoWay}"
         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}"
         ChangeKeyFramesLengthCommand="{xaml:Command PixiEditor.Animation.ChangeKeyFramesStartPos, UseProvided=True}"/>
         ChangeKeyFramesLengthCommand="{xaml:Command PixiEditor.Animation.ChangeKeyFramesStartPos, UseProvided=True}"/>