Browse Source

Merge pull request #711 from PixiEditor/cleanup

Animation mini player
Krzysztof Krysiński 7 months ago
parent
commit
974b632069

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit ce63fc3f4fac69fd8108f828a776dd0a36d05a47
+Subproject commit 63c826db08b9ea57cf6ae29718d94fd77951c73a

+ 1 - 1
src/PixiDocks

@@ -1 +1 @@
-Subproject commit 1bbf5698bd734cb263a364b6d08f52a9f004dec8
+Subproject commit 9190dfc8d59f5eaae40a80e34e75a1d5667dec83

+ 109 - 104
src/PixiEditor.UI.Common/Controls/Slider.axaml

@@ -6,113 +6,118 @@
         <Setter Property="Template">
             <Setter.Value>
                 <ControlTemplate TargetType="RepeatButton">
-                    <Border Background="Transparent" ></Border>
+                    <Border Background="Transparent"></Border>
                 </ControlTemplate>
             </Setter.Value>
         </Setter>
-  </ControlTheme>
+    </ControlTheme>
 
-  <ControlTheme x:Key="{x:Type Slider}"
-                TargetType="Slider">
-    <Style Selector="^:horizontal">
-      <Setter Property="MinWidth" Value="40" />
-      <Setter Property="MinHeight" Value="20" />
-      <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}"/>
-      <Setter Property="Template">
-        <ControlTemplate>
-            <Grid Name="grid">
-                <Border Margin="6, 0" CornerRadius="4" Background="{DynamicResource ThemeControlLowBrush}" Height="6"
-                        VerticalAlignment="Center"></Border>
-                <Canvas Margin="-6,-1">
-                    <Rectangle IsVisible="false" x:Name="PART_SelectionRange" Height="4.0" StrokeThickness="1.0"/>
-                </Canvas>
-            <Track Name="PART_Track"
-                   Grid.Row="1"
-                   IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
-                   Orientation="Horizontal">
-                <Track.DecreaseButton>
-                <RepeatButton Name="PART_DecreaseButton"
-                              Theme="{StaticResource SliderRepeatTrackTheme}" />
-              </Track.DecreaseButton>
-              <Track.IncreaseButton>
-                <RepeatButton Name="PART_IncreaseButton"
-                              Theme="{StaticResource SliderRepeatTrackTheme}" />
-              </Track.IncreaseButton>
-              <Thumb Name="thumb"
-                     MinWidth="20"
-                     MinHeight="20">
-                <Thumb.Template>
-                  <ControlTemplate>
-                      <Border Background="{DynamicResource ThumbBrush}" Width="8" Height="18" CornerRadius="4"/>
-                  </ControlTemplate>
-                </Thumb.Template>
-              </Thumb>
-            </Track>
-          </Grid>
-        </ControlTemplate>
-      </Setter>
-    </Style>
-    <Style Selector="^:vertical">
-      <Setter Property="MinWidth" Value="20" />
-      <Setter Property="MinHeight" Value="40" />
-      <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}"/>
-      <Setter Property="Template">
-        <ControlTemplate>
-            <Border CornerRadius="{TemplateBinding CornerRadius}">
-            <Grid>
-            <Grid.ColumnDefinitions>
-              <ColumnDefinition Width="Auto" />
-              <ColumnDefinition Width="Auto"
-                                MinWidth="26" />
-              <ColumnDefinition Width="Auto" />
-            </Grid.ColumnDefinitions>
-            <Border Name="TrackBackground"
-                    Grid.Column="1"
-                    Width="4"
-                    Margin="0,6"
-                    HorizontalAlignment="Center" />
-            <Track Name="PART_Track"
-                   Grid.Column="1"
-                   IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
-                   Orientation="Vertical">
-              <Track.DecreaseButton>
-                <RepeatButton Name="PART_DecreaseButton"
-                              Theme="{StaticResource SliderRepeatTrackTheme}" />
-              </Track.DecreaseButton>
-              <Track.IncreaseButton>
-                <RepeatButton Name="PART_IncreaseButton"
-                              Theme="{StaticResource SliderRepeatTrackTheme}" />
-              </Track.IncreaseButton>
-              <Thumb Name="thumb"
-                     MinWidth="20"
-                     MinHeight="20">
-                <Thumb.Template>
-                  <ControlTemplate>
-                      <Border Background="{DynamicResource ThumbBrush}" Width="18" Height="8" CornerRadius="4"/>
-                  </ControlTemplate>
-                </Thumb.Template>
-              </Thumb>
-            </Track>
-          </Grid>
-            </Border>
-        </ControlTemplate>
-      </Setter>
-    </Style>
+    <ControlTheme x:Key="{x:Type Slider}"
+                  TargetType="Slider">
+        <Style Selector="^:horizontal">
+            <Setter Property="MinWidth" Value="40" />
+            <Setter Property="MinHeight" Value="20" />
+            <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
+            <Setter Property="Template">
+                <ControlTemplate>
+                    <Grid Name="grid">
+                        <Border Margin="6, 0" CornerRadius="4" Background="{DynamicResource ThemeControlLowBrush}"
+                                Height="6"
+                                VerticalAlignment="Center">
+                        </Border>
+                        <Canvas Margin="-6,-1">
+                            <Rectangle IsVisible="false" x:Name="PART_SelectionRange" Height="4.0"
+                                       StrokeThickness="1.0" />
+                        </Canvas>
+                        <Track Name="PART_Track"
+                               Grid.Row="1"
+                               IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
+                               Orientation="Horizontal">
+                            <Track.DecreaseButton>
+                                <RepeatButton Name="PART_DecreaseButton"
+                                              Theme="{StaticResource SliderRepeatTrackTheme}" />
+                            </Track.DecreaseButton>
+                            <Track.IncreaseButton>
+                                <RepeatButton Name="PART_IncreaseButton"
+                                              Theme="{StaticResource SliderRepeatTrackTheme}" />
+                            </Track.IncreaseButton>
+                            <Thumb Name="thumb"
+                                   MinWidth="20"
+                                   MinHeight="20">
+                                <Thumb.Template>
+                                    <ControlTemplate>
+                                        <Border Background="{DynamicResource ThumbBrush}" Width="8" Height="18"
+                                                CornerRadius="4" />
+                                    </ControlTemplate>
+                                </Thumb.Template>
+                            </Thumb>
+                        </Track>
+                    </Grid>
+                </ControlTemplate>
+            </Setter>
+        </Style>
+        <Style Selector="^:vertical">
+            <Setter Property="MinWidth" Value="20" />
+            <Setter Property="MinHeight" Value="40" />
+            <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
+            <Setter Property="Template">
+                <ControlTemplate>
+                    <Border CornerRadius="{TemplateBinding CornerRadius}">
+                        <Grid>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="Auto" />
+                                <ColumnDefinition Width="Auto"
+                                                  MinWidth="26" />
+                                <ColumnDefinition Width="Auto" />
+                            </Grid.ColumnDefinitions>
+                            <Border Name="TrackBackground"
+                                    Grid.Column="1"
+                                    Width="4"
+                                    Margin="0,6"
+                                    HorizontalAlignment="Center" />
+                            <Track Name="PART_Track"
+                                   Grid.Column="1"
+                                   IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
+                                   Orientation="Vertical">
+                                <Track.DecreaseButton>
+                                    <RepeatButton Name="PART_DecreaseButton"
+                                                  Theme="{StaticResource SliderRepeatTrackTheme}" />
+                                </Track.DecreaseButton>
+                                <Track.IncreaseButton>
+                                    <RepeatButton Name="PART_IncreaseButton"
+                                                  Theme="{StaticResource SliderRepeatTrackTheme}" />
+                                </Track.IncreaseButton>
+                                <Thumb Name="thumb"
+                                       MinWidth="20"
+                                       MinHeight="20">
+                                    <Thumb.Template>
+                                        <ControlTemplate>
+                                            <Border Background="{DynamicResource ThumbBrush}" Width="18" Height="8"
+                                                    CornerRadius="4" />
+                                        </ControlTemplate>
+                                    </Thumb.Template>
+                                </Thumb>
+                            </Track>
+                        </Grid>
+                    </Border>
+                </ControlTemplate>
+            </Setter>
+        </Style>
 
-    <Style Selector="^ /template/ Track#PART_Track">
-      <Setter Property="Minimum" Value="{TemplateBinding Minimum}" />
-      <Setter Property="Maximum" Value="{TemplateBinding Maximum}" />
-      <Setter Property="Value" Value="{TemplateBinding Value, Mode=TwoWay}" />
-    </Style>
-    <Style Selector="^ /template/ Border#TrackBackground">
-      <Setter Property="BorderThickness" Value="2" />
-      <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLowBrush}" />
-    </Style>
-    <Style Selector="^ /template/ TickBar">
-      <Setter Property="Ticks" Value="{TemplateBinding Ticks}" />
-    </Style>
-    <Style Selector="^:disabled /template/ Grid#grid">
-      <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
-    </Style>
-  </ControlTheme>
+        <Style Selector="^ /template/ Track#PART_Track">
+            <Setter Property="Minimum" Value="{TemplateBinding Minimum}" />
+            <Setter Property="Maximum" Value="{TemplateBinding Maximum}" />
+            <Setter Property="Value" Value="{TemplateBinding Value, Mode=TwoWay}" />
+        </Style>
+        <Style Selector="^ /template/ Border#TrackBackground">
+            <Setter Property="BorderThickness" Value="2" />
+            <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLowBrush}" />
+        </Style>
+        <Style Selector="^ /template/ TickBar">
+            <Setter Property="Ticks" Value="{TemplateBinding Ticks}" />
+        </Style>
+        <Style Selector="^:disabled /template/ Grid#grid">
+            <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
+        </Style>
+    </ControlTheme>
 </ResourceDictionary>

BIN
src/PixiEditor.UI.Common/Fonts/PixiPerfect.ttf


+ 0 - 1
src/PixiEditor.UI.Common/PixiEditor.UI.Common.csproj

@@ -8,7 +8,6 @@
 
     <ItemGroup>
       <AvaloniaResource Include="Assets\*"/>
-      <AvaloniaResource Include="Fonts\pixiperfect.ttf" />
       <None Remove="Assets\Animations\LoadingIndicator.json" />
       <AvaloniaResource Include="Assets\Animations\LoadingIndicator.json" />
       <AvaloniaResource Include="Fonts\PixiPerfect.ttf" />

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

@@ -800,5 +800,8 @@
   "RENDER_PREVIEW": "Render preview",
   "OUTPUT_NAME": "Preview name",
   "CUSTOM_OUTPUT_NODE": "Preview Node",
-  "TOGGLE_HUD": "Toggle HUD"
+  "TOGGLE_HUD": "Toggle HUD",
+  "OPEN_TIMELINE": "Open timeline",
+  "OPEN_NODE_GRAPH": "Open node graph",
+  "TOGGLE_PLAY": "Play/Pause animation"
 }

BIN
src/PixiEditor/Fonts/nodeicons.ttf


+ 1 - 0
src/PixiEditor/Styles/PortingWipStyles.axaml

@@ -49,6 +49,7 @@
 
     <Style Selector="ToggleButton.PlayButton">
         <Setter Property="Content" Value="{DynamicResource icon-play}" />
+        <Setter Property="Background" Value="Transparent"/>
         <Setter Property="Template">
             <Setter.Value>
                 <ControlTemplate>

+ 31 - 5
src/PixiEditor/ViewModels/Dock/LayoutManager.cs

@@ -39,7 +39,7 @@ internal class LayoutManager
         PaletteViewerDockViewModel paletteViewerDockViewModel =
             new(mainViewModel.ColorsSubViewModel, mainViewModel.DocumentManagerSubViewModel);
         TimelineDockViewModel timelineDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
-        
+
         NodeGraphDockViewModel nodeGraphDockViewModel = new(mainViewModel.DocumentManagerSubViewModel);
         /*
         ChannelsDockViewModel channelsDockDockViewModel = new(mainViewModel.WindowSubViewModel);
@@ -56,7 +56,7 @@ internal class LayoutManager
         /*
         RegisterDockable(channelsDockDockViewModel);
         */
-        
+
         DefaultLayout = new LayoutTree
         {
             Root = new DockableTree
@@ -67,6 +67,10 @@ internal class LayoutManager
                     {
                         Id = "DocumentArea", FallbackContent = new CreateDocumentFallbackView(),
                     },
+                    SplitDirection = DockingDirection.Bottom,
+                    SecondSize = 300,
+                    AutoExpand = true,
+                    Second = new DockableArea() { Id = "TimelineArea", CloseRegionOnEmpty = false }
                 },
                 SecondSize = 360,
                 SplitDirection = DockingDirection.Right,
@@ -90,8 +94,7 @@ internal class LayoutManager
                         SplitDirection = DockingDirection.Bottom,
                         Second = new DockableArea
                         {
-                            Id = "LayersArea",
-                            Dockables = [ DockContext.CreateDockable(layersDockViewModel) ]
+                            Id = "LayersArea", Dockables = [DockContext.CreateDockable(layersDockViewModel)]
                         },
                     },
                     FirstSize = 0.66,
@@ -201,7 +204,30 @@ internal class LayoutManager
         IDockable? created = TryCreateDockable(id);
         if (created != null)
         {
-            DockContext.Float(created, 0, 0);
+            bool attached = false;
+            ActiveLayout.Root.Traverse(((element, tree) =>
+            {
+                if (element is IDockableHost host)
+                {
+                    if (element.Id == $"{id}Area" && !attached)
+                    {
+                        host.AddDockable(created);
+                        host.ActiveDockable = created;
+                        attached = true;
+                    }
+                    else if (id == NodeGraphDockViewModel.TabId && element.Id == "DocumentArea")
+                    {
+                        host.AddDockable(created);
+                        host.ActiveDockable = created;
+                        attached = true;
+                    }
+                }
+            }));
+
+            if (!attached)
+            {
+                DockContext.Float(created, 0, 0);
+            }
         }
     }
 }

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

@@ -185,6 +185,8 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         frameRateBindable = newFrameRate;
         OnPropertyChanged(nameof(FrameRateBindable));
         OnPropertyChanged(nameof(DefaultEndFrame));
+        OnPropertyChanged(nameof(LastFrame));
+        OnPropertyChanged(nameof(FramesCount));
     }
 
     public void SetActiveFrame(int newFrame)
@@ -222,6 +224,9 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
             keyFrame.SetStartFrame(newStartFrame);
             keyFrame.SetDuration(newDuration);
             keyFrames.NotifyCollectionChanged();
+            OnPropertyChanged(nameof(FirstFrame));
+            OnPropertyChanged(nameof(LastFrame));
+            OnPropertyChanged(nameof(FramesCount));
         }
     }
 
@@ -258,6 +263,9 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         }
         
         SortByLayers();
+        OnPropertyChanged(nameof(FirstFrame));
+        OnPropertyChanged(nameof(LastFrame));
+        OnPropertyChanged(nameof(FramesCount));
     }
 
     public void RemoveKeyFrame(Guid keyFrameId)
@@ -281,6 +289,10 @@ internal class AnimationDataViewModel : ObservableObject, IAnimationHandler
         });
 
         allCels.RemoveAll(x => x.Id == keyFrameId);
+        
+        OnPropertyChanged(nameof(FirstFrame));
+        OnPropertyChanged(nameof(LastFrame));
+        OnPropertyChanged(nameof(FramesCount));
     }
 
     public void AddSelectedKeyFrame(Guid keyFrameId)

+ 1 - 1
src/PixiEditor/ViewModels/Document/Nodes/CustomOutputNodeViewModel.cs

@@ -4,5 +4,5 @@ using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.ViewModels.Document.Nodes;
 
-[NodeViewModel("CUSTOM_OUTPUT_NODE", null, null)]
+[NodeViewModel("CUSTOM_OUTPUT_NODE", null, "\ue920")]
 internal class CustomOutputNodeViewModel : NodeViewModel<CustomOutputNode>;

+ 72 - 2
src/PixiEditor/ViewModels/SubViewModels/AnimationsViewModel.cs

@@ -1,4 +1,6 @@
-using Avalonia.Input;
+using System.ComponentModel;
+using Avalonia.Input;
+using Avalonia.Threading;
 using ChunkyImageLib;
 using PixiEditor.AnimationRenderer.Core;
 using PixiEditor.Models.AnalyticsAPI;
@@ -13,8 +15,76 @@ namespace PixiEditor.ViewModels.SubViewModels;
 [Command.Group("PixiEditor.Animations", "ANIMATIONS")]
 internal class AnimationsViewModel : SubViewModel<ViewModelMain>
 {
+    private DispatcherTimer _playTimer;
+
     public AnimationsViewModel(ViewModelMain owner) : base(owner)
     {
+        owner.DocumentManagerSubViewModel.ActiveDocumentChanged += (sender, args) =>
+        {
+            if (args.NewDocument != null)
+            {
+                InitTimer();
+                args.NewDocument.AnimationDataViewModel.PropertyChanged += AnimationDataViewModelOnPropertyChanged;
+            }
+
+            TogglePlayTimer(args.NewDocument?.AnimationDataViewModel.IsPlayingBindable ?? false);
+        };
+    }
+
+    private void AnimationDataViewModelOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+    {
+        if (e.PropertyName == nameof(AnimationDataViewModel.IsPlayingBindable))
+        {
+            TogglePlayTimer(Owner.DocumentManagerSubViewModel.ActiveDocument.AnimationDataViewModel.IsPlayingBindable);
+        }
+        else if (e.PropertyName == nameof(AnimationDataViewModel.FrameRateBindable))
+        {
+            InitTimer();
+        }
+    }
+
+    private void InitTimer()
+    {
+        if (_playTimer != null)
+        {
+            _playTimer.Stop();
+            _playTimer.Tick -= PlayTimerOnTick;
+        }
+        
+        var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        _playTimer =
+            new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(1000f / activeDocument.AnimationDataViewModel.FrameRateBindable) };
+        _playTimer.Tick += PlayTimerOnTick;
+    }
+
+    private void TogglePlayTimer(bool isPlaying)
+    {
+        if (isPlaying)
+        {
+            if (_playTimer is null)
+            {
+                return;
+            }
+
+            _playTimer.Start();
+        }
+        else
+        {
+            _playTimer?.Stop();
+        }
+    }
+
+    private void PlayTimerOnTick(object? sender, EventArgs e)
+    {
+        var activeDocument = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (activeDocument.AnimationDataViewModel.ActiveFrameBindable + 1 >= activeDocument.AnimationDataViewModel.LastFrame)
+        {
+            activeDocument.AnimationDataViewModel.ActiveFrameBindable = 1;
+        }
+        else
+        {
+            activeDocument.AnimationDataViewModel.ActiveFrameBindable++;
+        }
     }
 
     [Command.Basic("PixiEditor.Animation.NudgeActiveFrameNext", "CHANGE_ACTIVE_FRAME_NEXT",
@@ -154,7 +224,7 @@ internal class AnimationsViewModel : SubViewModel<ViewModelMain>
 
             return activeDocument.AnimationDataViewModel.FramesCount + 1;
         }
-        
+
         return active;
     }
 

+ 1 - 44
src/PixiEditor/Views/Animations/Timeline.cs

@@ -178,7 +178,6 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         : [];
 
     private ToggleButton? _playToggle;
-    private DispatcherTimer _playTimer;
     private Grid? _contentGrid;
     private TimelineSlider? _timelineSlider;
     private ScrollViewer? _timelineKeyFramesScroll;
@@ -198,17 +197,12 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
 
     static Timeline()
     {
-        IsPlayingProperty.Changed.Subscribe(IsPlayingChanged);
-        FpsProperty.Changed.Subscribe(FpsChanged);
         KeyFramesProperty.Changed.Subscribe(OnKeyFramesChanged);
         DefaultEndFrameProperty.Changed.Subscribe(OnDefaultEndFrameChanged);
     }
 
     public Timeline()
     {
-        _playTimer =
-            new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(1000f / Fps) };
-        _playTimer.Tick += PlayTimerOnTick;
         PressedKeyFrameCommand = new RelayCommand<PointerPressedEventArgs>(KeyFramePressed);
         ClearSelectedKeyFramesCommand = new RelayCommand<CelViewModel>(ClearSelectedKeyFrames);
         DraggedKeyFrameCommand = new RelayCommand<PointerEventArgs>(KeyFramesDragged);
@@ -360,17 +354,7 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         _timelineHeaderScroll!.Offset = new Vector(0, scrollViewer.Offset.Y);
     }
 
-    private void PlayTimerOnTick(object? sender, EventArgs e)
-    {
-        if (ActiveFrame >= EndFrame) 
-        {
-            ActiveFrame = 1;
-        }
-        else
-        {
-            ActiveFrame++;
-        }
-    }
+   
 
     private void PlayToggleOnClick(object? sender, RoutedEventArgs e)
     {
@@ -555,33 +539,6 @@ internal class Timeline : TemplatedControl, INotifyPropertyChanged
         return frame;
     }
 
-    private static void IsPlayingChanged(AvaloniaPropertyChangedEventArgs e)
-    {
-        if (e.Sender is not Timeline timeline)
-        {
-            return;
-        }
-
-        if (timeline.IsPlaying)
-        {
-            timeline._playTimer.Start();
-        }
-        else
-        {
-            timeline._playTimer.Stop();
-        }
-    }
-
-    private static void FpsChanged(AvaloniaPropertyChangedEventArgs e)
-    {
-        if (e.Sender is not Timeline timeline)
-        {
-            return;
-        }
-
-        timeline._playTimer.Interval = TimeSpan.FromMilliseconds(1000f / timeline.Fps);
-    }
-
     private static void OnKeyFramesChanged(AvaloniaPropertyChangedEventArgs e)
     {
         if (e.Sender is not Timeline timeline)

+ 2 - 2
src/PixiEditor/Views/LoadingWindow.axaml.cs

@@ -15,10 +15,10 @@ public partial class LoadingWindow : Window
 
     public static void ShowInNewThread()
     {
-        var thread = new Thread(ThreadStart) { IsBackground = true };
+        /*var thread = new Thread(ThreadStart) { IsBackground = true };
 
         thread.SetApartmentState(ApartmentState.STA);
-        thread.Start();
+        thread.Start();*/
     }
 
     public void SafeClose()

+ 43 - 34
src/PixiEditor/Views/Main/MainTitleBar.axaml

@@ -9,6 +9,7 @@
              xmlns:dialogs="clr-namespace:PixiEditor.Views.Dialogs"
              xmlns:input="clr-namespace:PixiEditor.Views.Input;assembly=PixiEditor.UI.Common"
              xmlns:menu="clr-namespace:PixiEditor.ViewModels.Menu"
+             xmlns:main="clr-namespace:PixiEditor.Views.Main"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:DataType="menu:MenuBarViewModel"
              x:Class="PixiEditor.Views.Main.MainTitleBar">
@@ -22,44 +23,52 @@
                 <Border BorderThickness="1" BorderBrush="{DynamicResource AccentColor}"
                         Padding="5 0" CornerRadius="5" Height="25"
                         IsVisible="{Binding Path=AdditionalContentSubViewModel.IsSupporterPackAvailable, FallbackValue=False}">
-                    <TextBlock VerticalAlignment="Center" ui:Translator.Key="PixiEditor.SupporterPack:AWESOME_SUPPORTER" />
+                    <TextBlock VerticalAlignment="Center"
+                               ui:Translator.Key="PixiEditor.SupporterPack:AWESOME_SUPPORTER" />
                 </Border>
             </dialogs:DialogTitleBar.AdditionalElement>
         </dialogs:DialogTitleBar>
         <Svg DockPanel.Dock="Left" Margin="10, 0, 0, 0" HorizontalAlignment="Left" Path="/Images/PixiEditorLogo.svg"
              Width="20" Height="20" />
-        <xaml:Menu
-            Margin="40, 0, 0, 0"
-            DockPanel.Dock="Left"
-            HorizontalAlignment="Left"
-            VerticalAlignment="Center"
-            ItemsSource="{Binding MenuEntries}"
-            Background="Transparent" />
-        <Border Width="300" Height="25"
-                Background="{DynamicResource ThemeBackgroundBrush}"
-                CornerRadius="5" BorderThickness="1"
-                Margin="10,0,0,0"
-                Cursor="IBeam">
-            <Border.Styles>
-                <Style Selector="Border">
-                    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
-                </Style>
-                <Style Selector="Border:pointerover">
-                    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
-                </Style>
-            </Border.Styles>
-            <Interaction.Behaviors>
-                <EventTriggerBehavior
-                    EventName="PointerPressed">
-                    <InvokeCommandAction
-                        Command="{xaml:Command PixiEditor.Search.Toggle}" />
-                </EventTriggerBehavior>
-            </Interaction.Behaviors>
-            <Grid Margin="5,0" VerticalAlignment="Center">
-                <TextBlock ui:Translator.Key="SEARCH" />
-                <TextBlock Text="{xaml:ShortcutBinding PixiEditor.Search.Toggle}"
-                           HorizontalAlignment="Right" />
-            </Grid>
-        </Border>
+        <StackPanel Orientation="Horizontal">
+            <xaml:Menu
+                Margin="40, 0, 0, 0"
+                DockPanel.Dock="Left"
+                HorizontalAlignment="Left"
+                VerticalAlignment="Center"
+                ItemsSource="{Binding MenuEntries}"
+                Background="Transparent" />
+            <Border DockPanel.Dock="Left" Height="25" Width="150"
+                    Background="{DynamicResource ThemeBackgroundBrush}"
+                    CornerRadius="5" BorderThickness="1"
+                    Margin="10,0,0,0"
+                    Cursor="IBeam">
+                <Border.Styles>
+                    <Style Selector="Border">
+                        <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
+                    </Style>
+                    <Style Selector="Border:pointerover">
+                        <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
+                    </Style>
+                </Border.Styles>
+                <Interaction.Behaviors>
+                    <EventTriggerBehavior
+                        EventName="PointerPressed">
+                        <InvokeCommandAction
+                            Command="{xaml:Command PixiEditor.Search.Toggle}" />
+                    </EventTriggerBehavior>
+                </Interaction.Behaviors>
+                <Grid Margin="5,0" VerticalAlignment="Center">
+                    <TextBlock ui:Translator.Key="SEARCH" />
+                    <TextBlock Text="{xaml:ShortcutBinding PixiEditor.Search.Toggle}"
+                               HorizontalAlignment="Right" />
+                </Grid>
+            </Border>
+        </StackPanel>
+        <main:MiniAnimationPlayer
+            ActiveFrame="{Binding ActiveDocument.AnimationDataViewModel.ActiveFrameBindable, Source={viewModels:MainVM DocumentManagerSVM}, Mode=TwoWay}"
+            FramesCount="{Binding ActiveDocument.AnimationDataViewModel.FramesCount, Source={viewModels:MainVM DocumentManagerSVM}}"
+            IsPlaying="{Binding ActiveDocument.AnimationDataViewModel.IsPlayingBindable, Source={viewModels:MainVM DocumentManagerSVM}, Mode=TwoWay}"
+            Margin="0 4" />
     </Grid>
 </UserControl>

+ 4 - 3
src/PixiEditor/Views/Main/MainTitleBar.axaml.cs

@@ -1,10 +1,11 @@
-using Avalonia.Controls;
+using Avalonia;
+using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 
 namespace PixiEditor.Views.Main;
 
-public partial class MainTitleBar : UserControl
-{
+public partial class MainTitleBar : UserControl {
+    
     public MainTitleBar()
     {
         InitializeComponent();

+ 83 - 0
src/PixiEditor/Views/Main/MiniAnimationPlayer.axaml

@@ -0,0 +1,83 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:main="clr-namespace:PixiEditor.Views.Main"
+             xmlns:xaml="clr-namespace:PixiEditor.Models.Commands.XAML"
+             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PixiEditor.Views.Main.MiniAnimationPlayer" Width="150">
+    <StackPanel Orientation="Horizontal" Spacing="5">
+        <Border Background="{DynamicResource ThemeBackgroundBrush}"
+                BorderBrush="{DynamicResource ThemeBorderMidBrush}"
+                BorderThickness="1"
+                CornerRadius="{DynamicResource ControlCornerRadius}">
+            <Grid
+                Width="75"
+                DataContext="{Binding RelativeSource={RelativeSource AncestorType=main:MiniAnimationPlayer, Mode=FindAncestor}}">
+                <Slider Minimum="1"
+                        IsSnapToTickEnabled="True"
+                        TickFrequency="1"
+                        Maximum="{Binding FramesCount}"
+                        Value="{Binding ActiveFrame, Mode=TwoWay}">
+                    <Slider.Template>
+                        <ControlTemplate>
+                            <Grid Name="grid">
+                                <Border CornerRadius="0" Background="Transparent"
+                                        Height="6"
+                                        Padding="0" Margin="0"
+                                        VerticalAlignment="Center">
+                                </Border>
+                                <Canvas Margin="-6,-1">
+                                    <Rectangle IsVisible="false" x:Name="PART_SelectionRange" Height="4.0"
+                                               StrokeThickness="1.0" />
+                                </Canvas>
+                                <Track Name="PART_Track"
+                                       Grid.Row="1"
+                                       IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
+                                       Orientation="Horizontal">
+                                    <Track.DecreaseButton>
+                                        <RepeatButton Name="PART_DecreaseButton"
+                                                      Theme="{StaticResource SliderRepeatTrackTheme}" />
+                                    </Track.DecreaseButton>
+                                    <Track.IncreaseButton>
+                                        <RepeatButton Name="PART_IncreaseButton"
+                                                      Theme="{StaticResource SliderRepeatTrackTheme}" />
+                                    </Track.IncreaseButton>
+                                    <Thumb Name="thumb"
+                                           Margin="0"
+                                           Height="22">
+                                        <Thumb.Template>
+                                            <ControlTemplate>
+                                                <Border Background="{DynamicResource ThemeAccentBrush}"
+                                                        Opacity="0.5"
+                                                        Width="2" Height="{TemplateBinding Height}"
+                                                        CornerRadius="0" />
+                                            </ControlTemplate>
+                                        </Thumb.Template>
+                                    </Thumb>
+                                </Track>
+                            </Grid>
+                        </ControlTemplate>
+                    </Slider.Template>
+                </Slider>
+                <ToggleButton Width="24" Foreground="{DynamicResource SelectedHandleBrush}"
+                              VerticalAlignment="Center"
+                              HorizontalAlignment="Center"
+                              ui:Translator.TooltipKey="TOGGLE_PLAY"
+                              Classes="PlayButton" IsChecked="{Binding IsPlaying, Mode=TwoWay}" />
+
+            </Grid>
+        </Border>
+        <Button Classes="pixi-icon" FontSize="20" Content="{DynamicResource icon-timeline}"
+                Command="{xaml:Command UseProvided=True, Name=PixiEditor.Window.ShowDockWindow}"
+                CommandParameter="Timeline" 
+                ui:Translator.TooltipKey="OPEN_TIMELINE"
+                />
+        <Button Classes="pixi-icon" FontSize="20" Content="{DynamicResource icon-nodes}"
+                Command="{xaml:Command UseProvided=True, Name=PixiEditor.Window.ShowDockWindow}"
+                CommandParameter="NodeGraph" 
+                ui:Translator.TooltipKey="OPEN_NODE_GRAPH"
+                />
+    </StackPanel>
+</UserControl>

+ 39 - 0
src/PixiEditor/Views/Main/MiniAnimationPlayer.axaml.cs

@@ -0,0 +1,39 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PixiEditor.Views.Main;
+
+public partial class MiniAnimationPlayer : UserControl
+{
+    public static readonly StyledProperty<int> ActiveFrameProperty = AvaloniaProperty.Register<MiniAnimationPlayer, int>("ActiveFrame");
+
+    public static readonly StyledProperty<int> FramesCountProperty =
+        AvaloniaProperty.Register<MiniAnimationPlayer, int>("FramesCount");
+
+    public static readonly StyledProperty<bool> IsPlayingProperty = AvaloniaProperty.Register<MiniAnimationPlayer, bool>(
+        nameof(IsPlaying));
+
+    public bool IsPlaying
+    {
+        get => GetValue(IsPlayingProperty);
+        set => SetValue(IsPlayingProperty, value);
+    }
+
+    public int FramesCount
+    {
+        get { return (int)GetValue(FramesCountProperty); }
+        set { SetValue(FramesCountProperty, value); }
+    }
+
+    public int ActiveFrame
+    {
+        get { return (int)GetValue(ActiveFrameProperty); }
+        set { SetValue(ActiveFrameProperty, value); }
+    }
+
+    public MiniAnimationPlayer()
+    {
+        InitializeComponent();
+    }
+}

+ 1 - 1
windows-x64-release-dev.yml

@@ -111,7 +111,7 @@ steps:
   displayName: Publish PixiEditor
   inputs:
     filePath: 'src/PixiEditor.Builder/build.ps1'
-    arguments: '--project-path "$(System.DefaultWorkingDirectory)\src\PixiEditor.Desktop" --build-configuration "$(buildConfiguration)" --runtime "$(buildPlatform)" -o "$(System.DefaultWorkingDirectory)\Builds\PixiEditor-$(architecture)-light\PixiEditor" --crash-report-webhook-url "$(crash-webhook-url)"'
+    arguments: '--project-path "$(System.DefaultWorkingDirectory)\src\PixiEditor.Desktop" --build-configuration "$(buildConfiguration)" --runtime "$(buildPlatform)" -o "$(System.DefaultWorkingDirectory)\Builds\PixiEditor-$(architecture)-light\PixiEditor" --analytics-url "https://api.pixieditor.net/analytics/" --project-path C:\Git\PixiEditor\src\PixiEditor.Desktop --extension-projects C:\Git\PixiEditor\src\PixiEditor.Beta --crash-report-webhook-url "$(crash-webhook-url)"'
     workingDirectory: 'src/PixiEditor.Builder'
 
 - task: ArchiveFiles@2

+ 1 - 1
windows-x64-release.yml

@@ -111,7 +111,7 @@ steps:
   displayName: Publish PixiEditor
   inputs:
     filePath: 'src/PixiEditor.Builder/build.ps1'
-    arguments: '--project-path "$(System.DefaultWorkingDirectory)\src\PixiEditor.Desktop" --build-configuration "$(buildConfiguration)" --runtime "$(buildPlatform)" -o "$(System.DefaultWorkingDirectory)\Builds\PixiEditor-$(architecture)-light\PixiEditor" --crash-report-webhook-url "$(crash-webhook-url)"'
+    arguments: '--project-path "$(System.DefaultWorkingDirectory)\src\PixiEditor.Desktop" --build-configuration "$(buildConfiguration)" --analytics-url "https://api.pixieditor.net/analytics/" --runtime "$(buildPlatform)" -o "$(System.DefaultWorkingDirectory)\Builds\PixiEditor-$(architecture)-light\PixiEditor" --crash-report-webhook-url "$(crash-webhook-url)"'
     workingDirectory: 'src/PixiEditor.Builder'
 
 - task: ArchiveFiles@2