Browse Source

TimelineSlider

flabbet 1 year ago
parent
commit
97897bf64c

+ 26 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/DurationToWidthConverter.cs

@@ -0,0 +1,26 @@
+using System.Globalization;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+
+internal class DurationToWidthConverter : SingleInstanceMultiValueConverter<DurationToWidthConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return Convert(new List<object?> { value }, targetType, parameter, culture);
+    }
+
+    public override object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (values.Count != 2)
+        {
+            throw new ArgumentException("DurationToWidthConverter requires 2 values");
+        }
+        
+        if(values[0] is int duration && values[1] is double scale)
+        {
+            return duration * scale;
+        }
+        
+        return 0;
+    }
+}

+ 26 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/TimelineSliderWidthToMaximumConverter.cs

@@ -0,0 +1,26 @@
+using System.Globalization;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+
+internal class TimelineSliderWidthToMaximumConverter : SingleInstanceMultiValueConverter<TimelineSliderWidthToMaximumConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return Convert(new List<object?> { value }, targetType, parameter, culture);
+    }
+
+    public override object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (values.Count != 2)
+        {
+            throw new ArgumentException("TimelineSliderWidthToMaximumConverter requires 2 values");
+        }
+
+        if (values[0] is double width && values[1] is double scale)
+        {
+            return width / scale;
+        }
+
+        return 0;
+    }
+}

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/IKeyFrameHandler.cs

@@ -4,7 +4,7 @@ namespace PixiEditor.AvaloniaUI.Models.Handlers;
 
 
 public interface IKeyFrameHandler
 public interface IKeyFrameHandler
 {
 {
-    public Surface? Preview { get; set; }
+    public Surface? PreviewSurface { get; set; }
     public int StartFrame { get; }
     public int StartFrame { get; }
     public int Duration { get; }
     public int Duration { get; }
     public Guid LayerGuid { get; }
     public Guid LayerGuid { get; }

+ 8 - 8
src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs

@@ -218,10 +218,10 @@ internal class MemberPreviewUpdater
                 member.PreviewSurface?.Dispose();
                 member.PreviewSurface?.Dispose();
                 member.PreviewSurface = null;
                 member.PreviewSurface = null;
 
 
-                keyFrame?.Preview?.Dispose();
+                keyFrame?.PreviewSurface?.Dispose();
                 if (keyFrame != null)
                 if (keyFrame != null)
                 {
                 {
-                    keyFrame.Preview = null;
+                    keyFrame.PreviewSurface = null;
                 }
                 }
             }
             }
             else
             else
@@ -229,16 +229,16 @@ internal class MemberPreviewUpdater
                 if (member.PreviewSurface is not null && member.PreviewSurface.Size.X == newSize.Value.previewSize.X && member.PreviewSurface.Size.Y == newSize.Value.previewSize.Y)
                 if (member.PreviewSurface is not null && member.PreviewSurface.Size.X == newSize.Value.previewSize.X && member.PreviewSurface.Size.Y == newSize.Value.previewSize.Y)
                 {
                 {
                     member.PreviewSurface!.DrawingSurface.Canvas.Clear();
                     member.PreviewSurface!.DrawingSurface.Canvas.Clear();
-                    keyFrame?.Preview?.DrawingSurface.Canvas.Clear();
+                    keyFrame?.PreviewSurface?.DrawingSurface.Canvas.Clear();
                 }
                 }
                 else
                 else
                 {
                 {
                     member.PreviewSurface?.Dispose();
                     member.PreviewSurface?.Dispose();
                     member.PreviewSurface = new Surface(newSize.Value.previewSize); // TODO: premul bgra8888 was here
                     member.PreviewSurface = new Surface(newSize.Value.previewSize); // TODO: premul bgra8888 was here
-                    keyFrame?.Preview?.Dispose();
+                    keyFrame?.PreviewSurface?.Dispose();
                     if (keyFrame != null)
                     if (keyFrame != null)
                     {
                     {
-                        keyFrame.Preview = new Surface(newSize.Value.previewSize);
+                        keyFrame.PreviewSurface = new Surface(newSize.Value.previewSize);
                     }
                     }
                 }
                 }
             }
             }
@@ -567,15 +567,15 @@ internal class MemberPreviewUpdater
     
     
     private void RenderAnimationFramePreview(IStructureMemberHandler memberVM, IKeyFrameHandler keyFrame)
     private void RenderAnimationFramePreview(IStructureMemberHandler memberVM, IKeyFrameHandler keyFrame)
     {
     {
-        if (keyFrame.Preview is null)
+        if (keyFrame.PreviewSurface is null)
         {
         {
             if(memberVM.PreviewSurface is not null)
             if(memberVM.PreviewSurface is not null)
-                keyFrame.Preview = new Surface(memberVM.PreviewSurface.Size);
+                keyFrame.PreviewSurface = new Surface(memberVM.PreviewSurface.Size);
             else
             else
                 return;
                 return;
         }
         }
 
 
-        keyFrame.Preview.DrawingSurface.Canvas.DrawSurface(memberVM.PreviewSurface.DrawingSurface, VecI.Zero, ReplacingPaint);
+        keyFrame.PreviewSurface.DrawingSurface.Canvas.DrawSurface(memberVM.PreviewSurface.DrawingSurface, VecI.Zero, ReplacingPaint);
     }
     }
 
 
     private void RenderMaskPreviews(
     private void RenderMaskPreviews(

+ 1 - 0
src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Controls.axaml

@@ -11,6 +11,7 @@
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/DocumentTabTemplate.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/DocumentTabTemplate.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/KeyFrame.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/KeyFrame.axaml"/>
+                <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/TimelineSlider.axaml"/>
             </ResourceDictionary.MergedDictionaries>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
         </ResourceDictionary>
     </Styles.Resources>
     </Styles.Resources>

+ 32 - 4
src/PixiEditor.AvaloniaUI/Styles/Templates/KeyFrame.axaml

@@ -1,13 +1,41 @@
 <ResourceDictionary xmlns="https://github.com/avaloniaui"
 <ResourceDictionary xmlns="https://github.com/avaloniaui"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:animations="clr-namespace:PixiEditor.AvaloniaUI.Views.Animations"
                     xmlns:animations="clr-namespace:PixiEditor.AvaloniaUI.Views.Animations"
-                    xmlns:visuals="clr-namespace:PixiEditor.AvaloniaUI.Views.Visuals">
+                    xmlns:visuals="clr-namespace:PixiEditor.AvaloniaUI.Views.Visuals"
+                    xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+                    xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters">
     <ControlTheme TargetType="animations:KeyFrame" x:Key="{x:Type animations:KeyFrame}">
     <ControlTheme TargetType="animations:KeyFrame" x:Key="{x:Type animations:KeyFrame}">
+        <Setter Property="ClipToBounds" Value="False"/>
         <Setter Property="Template">
         <Setter Property="Template">
             <ControlTemplate>
             <ControlTemplate>
-                <Grid>
-                    <visuals:SurfaceControl Surface="{Binding Item.Preview, RelativeSource={RelativeSource TemplatedParent}}"/>
-                </Grid>
+                <Border CornerRadius="{DynamicResource ControlCornerRadius}" Background="{DynamicResource ThemeBackgroundBrush1}" Height="40" ClipToBounds="False"
+                      IsVisible="{Binding !!Item.PreviewSurface, RelativeSource={RelativeSource TemplatedParent}}"
+                      BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderThickness="1">
+                    <Border.Width>
+                        <MultiBinding Converter="{converters:DurationToWidthConverter}">
+                            <Binding Path="Item.Duration" RelativeSource="{RelativeSource TemplatedParent}"/>
+                            <Binding Path="Scale" RelativeSource="{RelativeSource TemplatedParent}"/>
+                        </MultiBinding>
+                    </Border.Width>
+                    <Border CornerRadius="{DynamicResource ControlCornerRadius}" Margin="-15, 0, 0, 0" Width="30" Height="30" BorderThickness="1" VerticalAlignment="Center" HorizontalAlignment="Left" BorderBrush="{DynamicResource ThemeBorderMidBrush}" 
+                            RenderOptions.BitmapInterpolationMode="None">
+                        <Border.Background>
+                            <ImageBrush Source="/Images/CheckerTile.png" TileMode="Tile">
+                                <ImageBrush.Transform>
+                                    <ScaleTransform ScaleX="0.4" ScaleY="0.4"/>
+                                </ImageBrush.Transform>
+                            </ImageBrush>
+                        </Border.Background>
+                        <visuals:SurfaceControl Surface="{Binding Item.PreviewSurface, RelativeSource={RelativeSource TemplatedParent}}" Stretch="Uniform" Width="30" Height="30">
+                            <ui:RenderOptionsBindable.BitmapInterpolationMode>
+                                <MultiBinding Converter="{converters:WidthToBitmapScalingModeConverter}">
+                                    <Binding Path="Item.PreviewSurface.Size.X" RelativeSource="{RelativeSource TemplatedParent}"/>
+                                    <Binding RelativeSource="{RelativeSource Mode=Self}" Path="Bounds.Width"/>
+                                </MultiBinding>
+                            </ui:RenderOptionsBindable.BitmapInterpolationMode>
+                        </visuals:SurfaceControl>
+                    </Border>
+                </Border>
             </ControlTemplate>
             </ControlTemplate>
         </Setter>
         </Setter>
     </ControlTheme>
     </ControlTheme>

+ 19 - 7
src/PixiEditor.AvaloniaUI/Styles/Templates/Timeline.axaml

@@ -25,7 +25,7 @@
                         <RowDefinition Height="*" />
                         <RowDefinition Height="*" />
                     </Grid.RowDefinitions>
                     </Grid.RowDefinitions>
 
 
-                    <DockPanel Grid.Row="0" LastChildFill="True">
+                    <DockPanel Background="{DynamicResource ThemeBackgroundBrush1}" Grid.Row="0" LastChildFill="True">
                         <Button DockPanel.Dock="Left" Classes="pixi-icon"
                         <Button DockPanel.Dock="Left" Classes="pixi-icon"
                                 Content="{DynamicResource icon-plus-square}"
                                 Content="{DynamicResource icon-plus-square}"
                                 Command="{TemplateBinding NewKeyFrameCommand}" />
                                 Command="{TemplateBinding NewKeyFrameCommand}" />
@@ -33,19 +33,30 @@
                         <Button DockPanel.Dock="Left" Classes="pixi-icon"
                         <Button DockPanel.Dock="Left" Classes="pixi-icon"
                                 Content="{DynamicResource icon-duplicate}"
                                 Content="{DynamicResource icon-duplicate}"
                                 Command="{TemplateBinding DuplicateKeyFrameCommand}" />
                                 Command="{TemplateBinding DuplicateKeyFrameCommand}" />
+                        <TextBlock DockPanel.Dock="Left" Text="Scale:" />
+                        <Slider Minimum="1" Maximum="100" Value="{TemplateBinding Scale, Mode=TwoWay}" Width="100" />
+                        <TextBlock Text="{Binding Scale, RelativeSource={RelativeSource TemplatedParent}}" />
                         <Panel>
                         <Panel>
                             <ToggleButton HorizontalAlignment="Center" Content="Play" Name="PART_PlayToggle" />
                             <ToggleButton HorizontalAlignment="Center" Content="Play" Name="PART_PlayToggle" />
                         </Panel>
                         </Panel>
                     </DockPanel>
                     </DockPanel>
 
 
-                    <DockPanel Grid.Row="1" LastChildFill="True">
-                        <Slider Margin="10 0" TickFrequency="1" TickPlacement="BottomRight"
+                    <DockPanel Background="{DynamicResource ThemeBackgroundBrush1}" Grid.Row="1" LastChildFill="True">
+                        <animations:TimelineSlider Margin="10 0" TickFrequency="1" TickPlacement="TopLeft"
                                 SmallChange="1"
                                 SmallChange="1"
                                 LargeChange="10"
                                 LargeChange="10"
                                 IsSnapToTickEnabled="True"
                                 IsSnapToTickEnabled="True"
                                 Name="ActiveFrameSlider"
                                 Name="ActiveFrameSlider"
-                                Minimum="0"
-                                Maximum="{Binding KeyFrames.FrameCount, RelativeSource={RelativeSource TemplatedParent}}">
+                                Minimum="0">
+                            <animations:TimelineSlider.Maximum>
+                                <MultiBinding>
+                                    <MultiBinding.Converter>
+                                        <converters:TimelineSliderWidthToMaximumConverter />
+                                    </MultiBinding.Converter>
+                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Bounds.Width" />
+                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Scale" />
+                                </MultiBinding>
+                            </animations:TimelineSlider.Maximum>
                             <Interaction.Behaviors>
                             <Interaction.Behaviors>
                                 <behaviours:SliderUpdateBehavior
                                 <behaviours:SliderUpdateBehavior
                                     Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActiveFrame, Mode=OneWay}"
                                     Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActiveFrame, Mode=OneWay}"
@@ -55,13 +66,14 @@
                                     SetValueCommand="{xaml:Command PixiEditor.Animation.ActiveFrameSet, UseProvided=True}"
                                     SetValueCommand="{xaml:Command PixiEditor.Animation.ActiveFrameSet, UseProvided=True}"
                                     ValueFromSlider="{Binding ElementName=ActiveFrameSlider, Path=Value, Mode=TwoWay}" />
                                     ValueFromSlider="{Binding ElementName=ActiveFrameSlider, Path=Value, Mode=TwoWay}" />
                             </Interaction.Behaviors>
                             </Interaction.Behaviors>
-                        </Slider>
+                        </animations:TimelineSlider>
                     </DockPanel>
                     </DockPanel>
 
 
                     <ScrollViewer Grid.Row="2" HorizontalScrollBarVisibility="Auto">
                     <ScrollViewer Grid.Row="2" HorizontalScrollBarVisibility="Auto">
                         <TreeView ItemsSource="{TemplateBinding KeyFrames}">
                         <TreeView ItemsSource="{TemplateBinding KeyFrames}">
                             <TreeView.ItemContainerTheme>
                             <TreeView.ItemContainerTheme>
                                 <ControlTheme TargetType="TreeViewItem">
                                 <ControlTheme TargetType="TreeViewItem">
+                                    <Setter Property="ClipToBounds" Value="False"/>
                                     <Setter Property="Template">
                                     <Setter Property="Template">
                                         <ControlTemplate>
                                         <ControlTemplate>
                                             <StackPanel>
                                             <StackPanel>
@@ -88,7 +100,7 @@
                             </TreeView.ItemContainerTheme>
                             </TreeView.ItemContainerTheme>
                             <TreeView.ItemTemplate>
                             <TreeView.ItemTemplate>
                                 <TreeDataTemplate ItemsSource="{Binding Children}">
                                 <TreeDataTemplate ItemsSource="{Binding Children}">
-                                    <animations:KeyFrame Item="{Binding}" />
+                                    <animations:KeyFrame Scale="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:Timeline}}" Item="{Binding}" />
                                 </TreeDataTemplate>
                                 </TreeDataTemplate>
                             </TreeView.ItemTemplate>
                             </TreeView.ItemTemplate>
                             <TreeView.ItemsPanel>
                             <TreeView.ItemsPanel>

+ 95 - 0
src/PixiEditor.AvaloniaUI/Styles/Templates/TimelineSlider.axaml

@@ -0,0 +1,95 @@
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:animations="clr-namespace:PixiEditor.AvaloniaUI.Views.Animations">
+
+    <ControlTheme x:Key="SliderRepeatTrackTheme"
+                  TargetType="RepeatButton">
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="RepeatButton">
+                    <Border Background="Transparent"></Border>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </ControlTheme>
+
+    <ControlTheme x:Key="{x:Type animations:TimelineSlider}"
+                  TargetType="animations:TimelineSlider">
+        <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>
+                        <TickBar
+                            Name="TopTickBar"
+                            Ticks="{TemplateBinding Ticks}"
+                            TickFrequency="{TemplateBinding Slider.TickFrequency}"
+                            Orientation="{TemplateBinding Slider.Orientation}"
+                            Minimum="{TemplateBinding Slider.Minimum}"
+                            Maximum="{TemplateBinding Slider.Maximum}"
+                            Height="15"
+                            Margin="0,0,0,4"
+                            VerticalAlignment="Center"
+                            Placement="Top"
+                            Fill="{DynamicResource ThemeBackgroundBrush2}" />
+                        <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 ThemeAccentBrush}" Width="20"
+                                                Height="18"
+                                                CornerRadius="4">
+                                            <TextBlock
+                                                Text="{Binding Value, RelativeSource={RelativeSource FindAncestor, AncestorType=animations:TimelineSlider}}"
+                                                HorizontalAlignment="Center"
+                                                VerticalAlignment="Center" />
+                                        </Border>
+                                    </ControlTemplate>
+                                </Thumb.Template>
+                            </Thumb>
+                        </Track>
+                    </Grid>
+                </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>
+</ResourceDictionary>

+ 4 - 4
src/PixiEditor.AvaloniaUI/ViewModels/Document/KeyFrameViewModel.cs

@@ -6,12 +6,12 @@ namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 
 
 public abstract class KeyFrameViewModel(int startFrame, int duration, Guid layerGuid, Guid id) : ObservableObject, IKeyFrameHandler
 public abstract class KeyFrameViewModel(int startFrame, int duration, Guid layerGuid, Guid id) : ObservableObject, IKeyFrameHandler
 {
 {
-    private Surface? preview;
+    private Surface? previewSurface;
     
     
-    public Surface? Preview
+    public Surface? PreviewSurface
     {
     {
-        get => preview;
-        set => SetProperty(ref preview, value);
+        get => previewSurface;
+        set => SetProperty(ref previewSurface, value);
     }
     }
     
     
     public virtual int StartFrame { get; } = startFrame;
     public virtual int StartFrame { get; } = startFrame;

+ 8 - 0
src/PixiEditor.AvaloniaUI/Views/Animations/KeyFrame.cs

@@ -9,9 +9,17 @@ public class KeyFrame : TemplatedControl
     public static readonly StyledProperty<KeyFrameViewModel> ItemProperty = AvaloniaProperty.Register<KeyFrame, KeyFrameViewModel>(
     public static readonly StyledProperty<KeyFrameViewModel> ItemProperty = AvaloniaProperty.Register<KeyFrame, KeyFrameViewModel>(
         nameof(Item));
         nameof(Item));
 
 
+    public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<KeyFrame, double>(nameof(Scale), 100);
+
     public KeyFrameViewModel Item
     public KeyFrameViewModel Item
     {
     {
         get => GetValue(ItemProperty);
         get => GetValue(ItemProperty);
         set => SetValue(ItemProperty, value);
         set => SetValue(ItemProperty, value);
     }
     }
+
+    public double Scale
+    {
+        get { return (double)GetValue(ScaleProperty); }
+        set { SetValue(ScaleProperty, value); }
+    }
 }
 }

+ 9 - 0
src/PixiEditor.AvaloniaUI/Views/Animations/Timeline.cs

@@ -27,6 +27,15 @@ public class Timeline : TemplatedControl
         AvaloniaProperty.Register<Timeline, ICommand>(
         AvaloniaProperty.Register<Timeline, ICommand>(
             nameof(NewKeyFrameCommand));
             nameof(NewKeyFrameCommand));
 
 
+    public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<Timeline, double>(
+        nameof(Scale), 100);
+
+    public double Scale
+    {
+        get => GetValue(ScaleProperty);
+        set => SetValue(ScaleProperty, value);
+    }
+
     public ICommand NewKeyFrameCommand
     public ICommand NewKeyFrameCommand
     {
     {
         get => GetValue(NewKeyFrameCommandProperty);
         get => GetValue(NewKeyFrameCommandProperty);

+ 17 - 0
src/PixiEditor.AvaloniaUI/Views/Animations/TimelineSlider.cs

@@ -0,0 +1,17 @@
+using Avalonia;
+using Avalonia.Controls;
+
+namespace PixiEditor.AvaloniaUI.Views.Animations;
+
+public class TimelineSlider : Slider
+{
+    public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<TimelineSlider, double>(
+        nameof(Scale), 100);
+
+    public double Scale
+    {
+        get => GetValue(ScaleProperty);
+        set => SetValue(ScaleProperty, value);
+    }
+    protected override Type StyleKeyOverride => typeof(TimelineSlider);
+}

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

@@ -119,7 +119,6 @@ public class AnimationData : IReadOnlyAnimationData
                     }
                     }
                     else
                     else
                     {
                     {
-                        Console.WriteLine($"{ActiveFrame}");
                         keyFrame.ActiveFrameChanged(ActiveFrame);
                         keyFrame.ActiveFrameChanged(ActiveFrame);
                     }
                     }
                 }
                 }