Browse Source

Added mini animation player and moved timeline logic to view model

flabbet 7 months ago
parent
commit
f14b67342d

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

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

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

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

+ 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="200"
+                    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();

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

@@ -0,0 +1,74 @@
+<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"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PixiEditor.Views.Main.MiniAnimationPlayer" Width="150">
+    <Border Background="{DynamicResource ThemeBackgroundBrush}"
+            BorderBrush="{DynamicResource ThemeBorderMidBrush}"
+            BorderThickness="1"
+            CornerRadius="{DynamicResource ControlCornerRadius}">
+        <Grid
+            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="{DynamicResource ThemeControlLowBrush}"
+                                    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>
+            <StackPanel Orientation="Horizontal" Spacing="5" HorizontalAlignment="Center">
+                <Button Classes="pixi-icon" FontSize="20" Content="{DynamicResource icon-timeline}"
+                        Command="{xaml:Command UseProvided=True, Name=PixiEditor.Window.ShowDockWindow}"
+                        CommandParameter="Timeline" 
+                />
+                <ToggleButton Width="24" Foreground="{DynamicResource SelectedHandleBrush}" VerticalAlignment="Center"
+                              Classes="PlayButton" IsChecked="{Binding IsPlaying, Mode=TwoWay}" />
+                <Button Classes="pixi-icon" FontSize="20" Content="{DynamicResource icon-nodes}"
+                        Command="{xaml:Command UseProvided=True, Name=PixiEditor.Window.ShowDockWindow}"
+                        CommandParameter="NodeGraph" />
+            </StackPanel>
+        </Grid>
+    </Border>
+</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();
+    }
+}