Przeglądaj źródła

Merge pull request #1461 from jmacato/wip-animations

CSS-like Animations & Transitions
Steven Kirk 7 lat temu
rodzic
commit
0c36c1baab
100 zmienionych plików z 2861 dodań i 652 usunięć
  1. 2 2
      samples/ControlCatalog/MainView.xaml
  2. 2 2
      samples/ControlCatalog/Pages/CarouselPage.xaml
  3. 3 3
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  4. 14 2
      samples/ControlCatalog/SideBar.xaml
  5. 13 0
      samples/RenderTest/App.xaml.cs
  6. 2 2
      samples/RenderTest/MainWindow.xaml
  7. 122 1
      samples/RenderTest/Pages/AnimationsPage.xaml
  8. 3 66
      samples/RenderTest/Pages/AnimationsPage.xaml.cs
  9. 42 3
      samples/RenderTest/Pages/ClippingPage.xaml
  10. 0 13
      samples/RenderTest/Pages/ClippingPage.xaml.cs
  11. 0 24
      samples/RenderTest/Program.cs
  12. 15 2
      samples/RenderTest/SideBar.xaml
  13. 40 0
      samples/RenderTest/ViewModels/AnimationsPageViewModel.cs
  14. 1 1
      samples/RenderTest/ViewModels/MainWindowViewModel.cs
  15. 72 20
      src/Avalonia.Animation/Animatable.cs
  16. 0 134
      src/Avalonia.Animation/Animate.cs
  17. 113 23
      src/Avalonia.Animation/Animation.cs
  18. 0 45
      src/Avalonia.Animation/AnimationExtensions.cs
  19. 17 0
      src/Avalonia.Animation/AnimationSetter.cs
  20. 0 56
      src/Avalonia.Animation/Animation`1.cs
  21. 18 0
      src/Avalonia.Animation/AnimatorAttribute.cs
  22. 23 0
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  23. 256 0
      src/Avalonia.Animation/AnimatorStateMachine`1.cs
  24. 192 0
      src/Avalonia.Animation/Animator`1.cs
  25. 88 0
      src/Avalonia.Animation/Cue.cs
  26. 41 0
      src/Avalonia.Animation/DoubleAnimator.cs
  27. 21 0
      src/Avalonia.Animation/DoubleSetter.cs
  28. 23 0
      src/Avalonia.Animation/DoubleTransition.cs
  29. 20 0
      src/Avalonia.Animation/Easing/BackEaseIn.cs
  30. 31 0
      src/Avalonia.Animation/Easing/BackEaseInOut.cs
  31. 21 0
      src/Avalonia.Animation/Easing/BackEaseOut.cs
  32. 21 0
      src/Avalonia.Animation/Easing/BounceEaseIn.cs
  33. 28 0
      src/Avalonia.Animation/Easing/BounceEaseInOut.cs
  34. 19 0
      src/Avalonia.Animation/Easing/BounceEaseOut.cs
  35. 21 0
      src/Avalonia.Animation/Easing/CircularEaseIn.cs
  36. 29 0
      src/Avalonia.Animation/Easing/CircularEaseInOut.cs
  37. 22 0
      src/Avalonia.Animation/Easing/CircularEaseOut.cs
  38. 18 0
      src/Avalonia.Animation/Easing/CubicEaseIn.cs
  39. 28 0
      src/Avalonia.Animation/Easing/CubicEaseInOut.cs
  40. 19 0
      src/Avalonia.Animation/Easing/CubicEaseOut.cs
  41. 56 0
      src/Avalonia.Animation/Easing/Easing.cs
  42. 23 0
      src/Avalonia.Animation/Easing/EasingTypeConverter.cs
  43. 22 0
      src/Avalonia.Animation/Easing/ElasticEaseIn.cs
  44. 31 0
      src/Avalonia.Animation/Easing/ElasticEaseInOut.cs
  45. 22 0
      src/Avalonia.Animation/Easing/ElasticEaseOut.cs
  46. 21 0
      src/Avalonia.Animation/Easing/ExponentialEaseIn.cs
  47. 29 0
      src/Avalonia.Animation/Easing/ExponentialEaseInOut.cs
  48. 21 0
      src/Avalonia.Animation/Easing/ExponentialEaseOut.cs
  49. 17 0
      src/Avalonia.Animation/Easing/LinearEasing.cs
  50. 18 0
      src/Avalonia.Animation/Easing/QuadraticEaseIn.cs
  51. 27 0
      src/Avalonia.Animation/Easing/QuadraticEaseInOut.cs
  52. 18 0
      src/Avalonia.Animation/Easing/QuadraticEaseOut.cs
  53. 19 0
      src/Avalonia.Animation/Easing/QuarticEaseIn.cs
  54. 30 0
      src/Avalonia.Animation/Easing/QuarticEaseInOut.cs
  55. 20 0
      src/Avalonia.Animation/Easing/QuarticEaseOut.cs
  56. 19 0
      src/Avalonia.Animation/Easing/QuinticEaseIn.cs
  57. 29 0
      src/Avalonia.Animation/Easing/QuinticEaseInOut.cs
  58. 20 0
      src/Avalonia.Animation/Easing/QuinticEaseOut.cs
  59. 21 0
      src/Avalonia.Animation/Easing/SineEaseIn.cs
  60. 20 0
      src/Avalonia.Animation/Easing/SineEaseInOut.cs
  61. 22 0
      src/Avalonia.Animation/Easing/SineEaseOut.cs
  62. 14 0
      src/Avalonia.Animation/FillMode.cs
  63. 23 0
      src/Avalonia.Animation/FloatTransition.cs
  64. 17 0
      src/Avalonia.Animation/IAnimation.cs
  65. 8 0
      src/Avalonia.Animation/IAnimationSetter.cs
  66. 22 0
      src/Avalonia.Animation/IAnimator.cs
  67. 3 10
      src/Avalonia.Animation/IEasing.cs
  68. 0 24
      src/Avalonia.Animation/IEasing`1.cs
  69. 26 0
      src/Avalonia.Animation/ITransition.cs
  70. 23 0
      src/Avalonia.Animation/IntegerTransition.cs
  71. 78 0
      src/Avalonia.Animation/KeyFrame.cs
  72. 41 0
      src/Avalonia.Animation/KeyFramePair`1.cs
  73. 0 41
      src/Avalonia.Animation/LinearDoubleEasing.cs
  74. 0 35
      src/Avalonia.Animation/LinearEasing.cs
  75. 27 0
      src/Avalonia.Animation/PlayState.cs
  76. 32 0
      src/Avalonia.Animation/PlaybackDirection.cs
  77. 8 0
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  78. 0 50
      src/Avalonia.Animation/PropertyTransition.cs
  79. 202 0
      src/Avalonia.Animation/RepeatCount.cs
  80. 23 0
      src/Avalonia.Animation/RepeatCountTypeConverter.cs
  81. 123 0
      src/Avalonia.Animation/Timing.cs
  82. 68 0
      src/Avalonia.Animation/Transition`1.cs
  83. 4 4
      src/Avalonia.Animation/Transitions.cs
  84. 38 0
      src/Avalonia.Animation/Utils/BounceEaseUtils.cs
  85. 18 0
      src/Avalonia.Animation/Utils/DoubleUtils.cs
  86. 18 0
      src/Avalonia.Animation/Utils/EasingUtils.cs
  87. 5 0
      src/Avalonia.Base/Logging/LogArea.cs
  88. 6 6
      src/Avalonia.Controls/Carousel.cs
  89. 10 9
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  90. 6 29
      src/Avalonia.Controls/ProgressBar.cs
  91. 6 6
      src/Avalonia.Controls/TabControl.cs
  92. 1 1
      src/Avalonia.Styling/Avalonia.Styling.csproj
  93. 25 0
      src/Avalonia.Styling/Styling/Style.cs
  94. 2 1
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  95. 1 1
      src/Avalonia.Themes.Default/Carousel.xaml
  96. 1 1
      src/Avalonia.Themes.Default/TabControl.xaml
  97. 4 15
      src/Avalonia.Visuals/Animation/CrossFade.cs
  98. 7 20
      src/Avalonia.Visuals/Animation/PageSlide.cs
  99. 31 0
      src/Avalonia.Visuals/Animation/PointTransition.cs
  100. 35 0
      src/Avalonia.Visuals/Animation/ThicknessTransition.cs

+ 2 - 2
samples/ControlCatalog/MainView.xaml

@@ -2,9 +2,9 @@
         xmlns:pages="clr-namespace:ControlCatalog.Pages"
         xmlns:pages="clr-namespace:ControlCatalog.Pages"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <TabControl Classes="sidebar" Name="Sidebar">
   <TabControl Classes="sidebar" Name="Sidebar">
-    <TabControl.Transition>
+    <TabControl.PageTransition>
       <CrossFade Duration="0.25"/>
       <CrossFade Duration="0.25"/>
-    </TabControl.Transition>
+    </TabControl.PageTransition>
     <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
     <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
     <TabItem Header="Border"><pages:BorderPage/></TabItem>
     <TabItem Header="Border"><pages:BorderPage/></TabItem>
     <TabItem Header="Button"><pages:ButtonPage/></TabItem>
     <TabItem Header="Button"><pages:ButtonPage/></TabItem>

+ 2 - 2
samples/ControlCatalog/Pages/CarouselPage.xaml

@@ -8,9 +8,9 @@
         <Path Data="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" Fill="Black"/>
         <Path Data="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" Fill="Black"/>
       </Button>
       </Button>
       <Carousel Name="carousel">
       <Carousel Name="carousel">
-        <Carousel.Transition>
+        <Carousel.PageTransition>
           <PageSlide Duration="0.25" Orientation="Vertical" />
           <PageSlide Duration="0.25" Orientation="Vertical" />
-        </Carousel.Transition>
+        </Carousel.PageTransition>
         <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"/>
         <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"/>
         <Image Source="resm:ControlCatalog.Assets.hirsch-899118_640.jpg"/>
         <Image Source="resm:ControlCatalog.Assets.hirsch-899118_640.jpg"/>
         <Image Source="resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg"/>
         <Image Source="resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg"/>

+ 3 - 3
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@@ -37,13 +37,13 @@ namespace ControlCatalog.Pages
             switch (_transition.SelectedIndex)
             switch (_transition.SelectedIndex)
             {
             {
                 case 0:
                 case 0:
-                    _carousel.Transition = null;
+                    _carousel.PageTransition = null;
                     break;
                     break;
                 case 1:
                 case 1:
-                    _carousel.Transition = new PageSlide(TimeSpan.FromSeconds(0.25), _orientation.SelectedIndex == 0 ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical);
+                    _carousel.PageTransition = new PageSlide(TimeSpan.FromSeconds(0.25), _orientation.SelectedIndex == 0 ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical);
                     break;
                     break;
                 case 2:
                 case 2:
-                    _carousel.Transition = new CrossFade(TimeSpan.FromSeconds(0.25));
+                    _carousel.PageTransition = new CrossFade(TimeSpan.FromSeconds(0.25));
                     break;
                     break;
             }
             }
         }
         }

+ 14 - 2
samples/ControlCatalog/SideBar.xaml

@@ -1,4 +1,5 @@
-<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
   <Style Selector="TabControl.sidebar">
   <Style Selector="TabControl.sidebar">
     <Setter Property="Template">
     <Setter Property="Template">
       <ControlTemplate>
       <ControlTemplate>
@@ -20,7 +21,7 @@
                     MemberSelector="{x:Static TabControl.ContentSelector}"
                     MemberSelector="{x:Static TabControl.ContentSelector}"
                     Items="{TemplateBinding Items}"
                     Items="{TemplateBinding Items}"
                     SelectedIndex="{TemplateBinding Path=SelectedIndex}"
                     SelectedIndex="{TemplateBinding Path=SelectedIndex}"
-                    Transition="{TemplateBinding Transition}"
+                    PageTransition="{TemplateBinding PageTransition}"
                     Grid.Row="1"/>
                     Grid.Row="1"/>
         </DockPanel>
         </DockPanel>
       </ControlTemplate>
       </ControlTemplate>
@@ -32,9 +33,20 @@
     <Setter Property="FontSize" Value="14"/>
     <Setter Property="FontSize" Value="14"/>
     <Setter Property="Margin" Value="0"/>
     <Setter Property="Margin" Value="0"/>
     <Setter Property="Padding" Value="16"/>
     <Setter Property="Padding" Value="16"/>
+    <Setter Property="Opacity" Value="0.5"/>
+    <Setter Property="Transitions">
+      <Transitions>
+        <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
+      </Transitions>
+    </Setter>
+  </Style>
+
+  <Style Selector="TabControl.sidebar TabStripItem:pointerover">
+    <Setter Property="Opacity" Value="1"/>
   </Style>
   </Style>
 
 
   <Style Selector="TabControl.sidebar TabStripItem:selected">
   <Style Selector="TabControl.sidebar TabStripItem:selected">
     <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
     <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
+    <Setter Property="Opacity" Value="1"/>
   </Style>
   </Style>
 </Styles>
 </Styles>

+ 13 - 0
samples/RenderTest/App.xaml.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using Avalonia;
 using Avalonia;
+using Avalonia.Logging.Serilog;
 using Avalonia.Markup.Xaml;
 using Avalonia.Markup.Xaml;
 
 
 namespace RenderTest
 namespace RenderTest
@@ -12,5 +13,17 @@ namespace RenderTest
         {
         {
             AvaloniaXamlLoader.Load(this);
             AvaloniaXamlLoader.Load(this);
         }
         }
+
+        // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
+        // again.
+        static void Main(string[] args) => BuildAvaloniaApp().Start<MainWindow>();
+
+        // App configuration, used by the entry point and previewer
+        static AppBuilder BuildAvaloniaApp()
+           => AppBuilder.Configure<App>()
+                .UsePlatformDetect()
+                .UseReactiveUI()
+                .LogToDebug();
+
     }
     }
 }
 }

+ 2 - 2
samples/RenderTest/MainWindow.xaml

@@ -24,9 +24,9 @@
     </Menu>
     </Menu>
     
     
     <TabControl Classes="sidebar">
     <TabControl Classes="sidebar">
-      <TabControl.Transition>
+      <TabControl.PageTransition>
         <CrossFade Duration="0.25"/>
         <CrossFade Duration="0.25"/>
-      </TabControl.Transition>
+      </TabControl.PageTransition>
       <TabItem Header="Animations"><pages:AnimationsPage/></TabItem>
       <TabItem Header="Animations"><pages:AnimationsPage/></TabItem>
       <TabItem Header="Clipping"><pages:ClippingPage/></TabItem>
       <TabItem Header="Clipping"><pages:ClippingPage/></TabItem>
       <TabItem Header="Drawing"><pages:DrawingPage/></TabItem>
       <TabItem Header="Drawing"><pages:DrawingPage/></TabItem>

+ 122 - 1
samples/RenderTest/Pages/AnimationsPage.xaml

@@ -1,2 +1,123 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl 
+  xmlns="https://github.com/avaloniaui" 
+  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <UserControl.Styles>
+    <Styles>
+      <Styles.Resources>
+        <Template x:Key="Acorn">
+          <Path Fill="White" Stretch="Uniform" 
+          Data="F1 M 16.6309,18.6563C 17.1309,
+                8.15625 29.8809,14.1563 29.8809,
+                14.1563C 30.8809,11.1563 34.1308,
+                11.4063 34.1308,11.4063C 33.5,12 
+                34.6309,13.1563 34.6309,13.1563C 
+                32.1309,13.1562 31.1309,14.9062 
+                31.1309,14.9062C 41.1309,23.9062
+                32.6309,27.9063 32.6309,27.9062C 
+                24.6309,24.9063 21.1309,22.1562 
+                16.6309,18.6563 Z M 16.6309,19.9063C
+                21.6309,24.1563 25.1309,26.1562 
+                31.6309,28.6562C 31.6309,28.6562
+                26.3809,39.1562 18.3809,36.1563C
+                18.3809,36.1563 18,38 16.3809,36.9063C 
+                15,36 16.3809,34.9063 16.3809,34.9063C
+                16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z"/>
+        </Template>
+        <Template x:Key="Heart">
+          <Path Fill="Red" Stretch="Uniform" Data="
+          M 272.70141,238.71731 
+          C 206.46141,238.71731 152.70146,292.4773 152.70146,358.71731 
+          C 152.70146,493.47282 288.63461,528.80461 381.26391,662.02535 
+          C 468.83815,529.62199 609.82641,489.17075 609.82641,358.71731 
+          C 609.82641,292.47731 556.06651,238.7173 489.82641,238.71731 
+          C 441.77851,238.71731 400.42481,267.08774 381.26391,307.90481 
+          C 362.10311,267.08773 320.74941,238.7173 272.70141,238.71731 z "/>
+        </Template>
+      </Styles.Resources>
+      <Style Selector="Border.Test">
+        <Setter Property="Margin" Value="15"/>
+        <Setter Property="Width" Value="100"/>
+        <Setter Property="Height" Value="100"/>
+        <Setter Property="Child" Value="{StaticResource Acorn}"/>
+      </Style>
+      <Style Selector="Border.Rect1:pointerover">
+        <Style.Animations>
+          <Animation Duration="0:0:2.5" 
+                     RepeatCount="4" 
+                     FillMode="None"
+                     PlaybackDirection="AlternateReverse" 
+                     Easing="SineEaseInOut">
+            <KeyFrame Cue="20%">
+              <TransformSetter Property="RotateTransform.Angle" Value="45"/>
+             </KeyFrame>
+            <KeyFrame Cue="50%">
+              <TransformSetter Property="ScaleTransform.ScaleX" Value="1.5"/>
+             </KeyFrame>
+            <KeyFrame Cue="80%">
+              <TransformSetter Property="RotateTransform.Angle" Value="120"/>
+            </KeyFrame>
+          </Animation>
+        </Style.Animations>
+      </Style>
+      <Style Selector="Border.Rect2:pointerover">
+        <Style.Animations>
+          <Animation Duration="0:0:0.5" Easing="SineEaseInOut">
+            <KeyFrame Cue="50%">
+              <TransformSetter Property="ScaleTransform.ScaleX" Value="0.8"/>
+              <TransformSetter Property="ScaleTransform.ScaleY" Value="0.8"/>
+            </KeyFrame>
+          </Animation>
+        </Style.Animations>
+      </Style>
+      <Style Selector="Border.Rect3">
+        <Setter Property="Child" Value="{StaticResource Heart}"/>
+        <Style.Animations>
+          <Animation Duration="0:0:0.5"
+                     Easing="QuadraticEaseInOut"
+                     RepeatCount="Loop">
+            <KeyFrame Cue="50%">
+              <TransformSetter Property="ScaleTransform.ScaleX" Value="0.8"/>
+              <TransformSetter Property="ScaleTransform.ScaleY" Value="0.8"/>
+            </KeyFrame>
+           </Animation>
+        </Style.Animations>
+      </Style>
+      <Style Selector="Border.Rect4:pointerover">
+        <Style.Animations>
+          <Animation Duration="0:0:3" Easing="BounceEaseInOut"> 
+            <KeyFrame Cue="48%">
+              <TransformSetter Property="TranslateTransform.Y" Value="-100"/>
+            </KeyFrame>
+          </Animation>
+        </Style.Animations>
+      </Style>
+      <Style Selector="Border.Rect5:pointerover">
+        <Style.Animations>
+          <Animation Duration="0:0:3" Easing="CircularEaseInOut">
+            <KeyFrame Cue="25%">
+              <TransformSetter Property="SkewTransform.AngleX" Value="-20"/>
+            </KeyFrame>
+            <KeyFrame Cue="75%">
+              <TransformSetter Property="SkewTransform.AngleX" Value="20"/>
+            </KeyFrame>
+          </Animation>
+        </Style.Animations>
+      </Style>
+    </Styles>
+  </UserControl.Styles>
+  <Grid>
+    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False">
+      <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
+        <TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock>
+        <Button Content="{Binding PlayStateText}" Command="{Binding ToggleGlobalPlayState}"/>
+      </StackPanel>
+      <WrapPanel ClipToBounds="False">
+        <Border Classes="Test Rect1" Background="DarkRed"/>
+        <Border Classes="Test Rect2" Background="Magenta"/>
+        <Border Classes="Test Rect3"/>
+        <Border Classes="Test Rect4" Background="Navy"/>
+        <Border Classes="Test Rect5" Background="SeaGreen"/>
+      </WrapPanel>
+    </StackPanel>
+  </Grid>
 </UserControl>
 </UserControl>

+ 3 - 66
samples/RenderTest/Pages/AnimationsPage.xaml.cs

@@ -7,6 +7,7 @@ using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Input;
 using Avalonia.Markup.Xaml;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
 using Avalonia.Media;
+using RenderTest.ViewModels;
 
 
 namespace RenderTest.Pages
 namespace RenderTest.Pages
 {
 {
@@ -14,77 +15,13 @@ namespace RenderTest.Pages
     {
     {
         public AnimationsPage()
         public AnimationsPage()
         {
         {
-            this.InitializeComponent();
-            this.CreateAnimations();
+            InitializeComponent();
+            this.DataContext = new AnimationsPageViewModel();
         }
         }
 
 
         private void InitializeComponent()
         private void InitializeComponent()
         {
         {
             AvaloniaXamlLoader.Load(this);
             AvaloniaXamlLoader.Load(this);
         }
         }
-
-        private void CreateAnimations()
-        {
-            const int Count = 100;
-            var panel = new WrapPanel();
-
-            for (var i = 0; i < Count; ++i)
-            {
-                Ellipse ellipse;
-                var element = new Panel
-                {
-                    Children =
-                    {
-                        (ellipse = new Ellipse
-                        {
-                            Name = $"ellipse{i}",
-                            Width = 100,
-                            Height = 100,
-                            Fill = Brushes.Blue,
-                        }),
-                        new Path
-                        {
-                            Data = StreamGeometry.Parse(
-                                "F1 M 16.6309,18.6563C 17.1309,8.15625 29.8809,14.1563 29.8809,14.1563C 30.8809,11.1563 34.1308,11.4063 34.1308,11.4063C 33.5,12 34.6309,13.1563 34.6309,13.1563C 32.1309,13.1562 31.1309,14.9062 31.1309,14.9062C 41.1309,23.9062 32.6309,27.9063 32.6309,27.9062C 24.6309,24.9063 21.1309,22.1562 16.6309,18.6563 Z M 16.6309,19.9063C 21.6309,24.1563 25.1309,26.1562 31.6309,28.6562C 31.6309,28.6562 26.3809,39.1562 18.3809,36.1563C 18.3809,36.1563 18,38 16.3809,36.9063C 15,36 16.3809,34.9063 16.3809,34.9063C 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z"),
-                            Fill = Brushes.Green,
-                            HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
-                            VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
-                            RenderTransform = new ScaleTransform(2, 2),
-                        }
-                    },
-                    Margin = new Thickness(4),
-                    RenderTransform = new ScaleTransform(),
-                };
-
-                var start = Animate.Stopwatch.Elapsed;
-                var index = i % (Count / 2);
-                var degrees = Animate.Timer
-                    .Select(x => (x - start).TotalSeconds)
-                    .Where(x => (x % Count) >= index && (x % Count) < index + 1)
-                    .Select(x => (x % 1) / 1);
-
-                element.RenderTransform.Bind(
-                    ScaleTransform.ScaleXProperty,
-                    degrees,
-                    BindingPriority.Animation);
-
-                ellipse.PointerEnter += Ellipse_PointerEnter;
-                ellipse.PointerLeave += Ellipse_PointerLeave;
-
-                panel.Children.Add(element);
-            }
-
-            Content = panel;
-        }
-
-        private void Ellipse_PointerEnter(object sender, PointerEventArgs e)
-        {
-            ((Ellipse)sender).Fill = Brushes.Red;
-        }
-
-        private void Ellipse_PointerLeave(object sender, PointerEventArgs e)
-        {
-            ((Ellipse)sender).Fill = Brushes.Blue;
-        }
     }
     }
 }
 }

+ 42 - 3
samples/RenderTest/Pages/ClippingPage.xaml

@@ -1,13 +1,52 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl 
+  xmlns="https://github.com/avaloniaui">
   <Grid ColumnDefinitions="Auto" RowDefinitions="Auto,Auto">
   <Grid ColumnDefinitions="Auto" RowDefinitions="Auto,Auto">
-    <Border Name="clipped"
+    <Grid.Styles>
+      <Styles>
+        <Style Selector="Border#clipped :pointerover">
+          <Setter Property="Border.Background" Value="Crimson"/>
+        </Style>
+        <Style Selector="Border#clipChild">
+          <Style.Animations>
+            <Animation Duration="0:0:2" RepeatCount="Loop">
+              <KeyFrame Cue="100%">
+                <TransformSetter Property="RotateTransform.Angle" Value="360"/>
+              </KeyFrame>
+            </Animation>
+          </Style.Animations>
+        </Style>
+      </Styles>
+    </Grid.Styles>
+    <Border Name="clipped" 
             Background="Yellow"
             Background="Yellow"
             Width="100"
             Width="100"
             Height="100"
             Height="100"
-            Clip="M 58.625 0.07421875 C 50.305778 0.26687364 42.411858 7.0346526 41.806641 15.595703 C 42.446442 22.063923 39.707425 13.710754 36.982422 12.683594 C 29.348395 6.1821635 16.419398 8.4359222 11.480469 17.195312 C 6.0935256 25.476803 9.8118851 37.71125 18.8125 41.6875 C 9.1554771 40.62945 -0.070876925 49.146842 0.21679688 58.857422 C 0.21545578 60.872512 0.56758794 62.88911 1.2617188 64.78125 C 4.3821886 74.16708 16.298268 78.921772 25.03125 74.326172 C 28.266843 72.062552 26.298191 74.214838 25.414062 76.398438 C 21.407348 85.589198 27.295992 97.294293 37.097656 99.501953 C 46.864883 102.3541 57.82177 94.726518 58.539062 84.580078 C 58.142158 79.498998 59.307538 83.392694 61.207031 85.433594 C 67.532324 93.056874 80.440232 93.192029 86.882812 85.630859 C 93.836392 78.456939 92.396838 65.538666 84.115234 60.009766 C 79.783641 57.904836 83.569793 58.802369 86.375 58.193359 C 96.383335 56.457569 102.87506 44.824101 99.083984 35.394531 C 95.963498 26.008711 84.047451 21.254079 75.314453 25.849609 C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344 C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812 C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z ">
+            Clip="M 58.625 0.07421875
+                  C 50.305778 0.26687364 42.411858 7.0346526 41.806641 15.595703
+                  C 42.446442 22.063923 39.707425 13.710754 36.982422 12.683594
+                  C 29.348395 6.1821635 16.419398 8.4359222 11.480469 17.195312
+                  C 6.0935256 25.476803 9.8118851 37.71125 18.8125 41.6875
+                  C 9.1554771 40.62945 -0.070876925 49.146842 0.21679688 58.857422 
+                  C 0.21545578 60.872512 0.56758794 62.88911 1.2617188 64.78125 
+                  C 4.3821886 74.16708 16.298268 78.921772 25.03125 74.326172 
+                  C 28.266843 72.062552 26.298191 74.214838 25.414062 76.398438
+                  C 21.407348 85.589198 27.295992 97.294293 37.097656 99.501953 
+                  C 46.864883 102.3541 57.82177 94.726518 58.539062 84.580078 
+                  C 58.142158 79.498998 59.307538 83.392694 61.207031 85.433594 
+                  C 67.532324 93.056874 80.440232 93.192029 86.882812 85.630859 
+                  C 93.836392 78.456939 92.396838 65.538666 84.115234 60.009766 
+                  C 79.783641 57.904836 83.569793 58.802369 86.375 58.193359 
+                  C 96.383335 56.457569 102.87506 44.824101 99.083984 35.394531 
+                  C 95.963498 26.008711 84.047451 21.254079 75.314453 25.849609
+                  C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344 
+                  C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812
+                  C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z ">
       <Border Name="clipChild" Background="{DynamicResource ThemeAccentBrush}" Margin="4">
       <Border Name="clipChild" Background="{DynamicResource ThemeAccentBrush}" Margin="4">
         <!-- Setting opacity puts the TextBox on a new layer -->
         <!-- Setting opacity puts the TextBox on a new layer -->
         <TextBox Text="Avalonia" Opacity="0.9" VerticalAlignment="Center"/>
         <TextBox Text="Avalonia" Opacity="0.9" VerticalAlignment="Center"/>
+          <Border.RenderTransform>
+            <RotateTransform/>
+          </Border.RenderTransform>
       </Border>
       </Border>
     </Border>
     </Border>
     <CheckBox Name="useMask" IsChecked="True" Grid.Row="1">Apply Geometry Clip</CheckBox>
     <CheckBox Name="useMask" IsChecked="True" Grid.Row="1">Apply Geometry Clip</CheckBox>

+ 0 - 13
samples/RenderTest/Pages/ClippingPage.xaml.cs

@@ -16,7 +16,6 @@ namespace RenderTest.Pages
         public ClippingPage()
         public ClippingPage()
         {
         {
             InitializeComponent();
             InitializeComponent();
-            CreateAnimations();
             WireUpCheckbox();
             WireUpCheckbox();
         }
         }
 
 
@@ -25,18 +24,6 @@ namespace RenderTest.Pages
             AvaloniaXamlLoader.Load(this);
             AvaloniaXamlLoader.Load(this);
         }
         }
 
 
-        private void CreateAnimations()
-        {
-            var clipped = this.FindControl<Border>("clipChild");
-            var degrees = Animate.Timer.Select(x => x.TotalMilliseconds / 5);
-            clipped.RenderTransform = new RotateTransform();
-            clipped.RenderTransform.Bind(RotateTransform.AngleProperty, degrees, BindingPriority.Animation);
-            clipped.Bind(
-                Border.BackgroundProperty,
-                clipped.GetObservable(Control.IsPointerOverProperty)
-                    .Select(x => x ? Brushes.Crimson : AvaloniaProperty.UnsetValue));
-        }
-
         private void WireUpCheckbox()
         private void WireUpCheckbox()
         {
         {
             var useMask = this.FindControl<CheckBox>("useMask");
             var useMask = this.FindControl<CheckBox>("useMask");

+ 0 - 24
samples/RenderTest/Program.cs

@@ -1,24 +0,0 @@
-using System;
-using System.Linq;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Logging.Serilog;
-using Avalonia.Platform;
-using Serilog;
-
-namespace RenderTest
-{
-    internal class Program
-    {
-        static void Main(string[] args)
-        {
-            // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
-            // again.
-            AppBuilder.Configure<App>()
-                .UsePlatformDetect()
-                .UseReactiveUI()
-                .LogToDebug()
-                .Start<MainWindow>();
-        }
-    }
-}

+ 15 - 2
samples/RenderTest/SideBar.xaml

@@ -1,4 +1,6 @@
-<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:a="clr-namespace:Avalonia.Media.Animations;assembly=Avalonia.Media.Animations">
   <Style Selector="TabControl.sidebar">
   <Style Selector="TabControl.sidebar">
     <Setter Property="Template">
     <Setter Property="Template">
       <ControlTemplate>
       <ControlTemplate>
@@ -20,7 +22,7 @@
                     MemberSelector="{x:Static TabControl.ContentSelector}"
                     MemberSelector="{x:Static TabControl.ContentSelector}"
                     Items="{TemplateBinding Items}"
                     Items="{TemplateBinding Items}"
                     SelectedIndex="{TemplateBinding Path=SelectedIndex}"
                     SelectedIndex="{TemplateBinding Path=SelectedIndex}"
-                    Transition="{TemplateBinding Transition}"
+                    PageTransition="{TemplateBinding PageTransition}"
                     Grid.Row="1"/>
                     Grid.Row="1"/>
         </DockPanel>
         </DockPanel>
       </ControlTemplate>
       </ControlTemplate>
@@ -32,9 +34,20 @@
     <Setter Property="FontSize" Value="14"/>
     <Setter Property="FontSize" Value="14"/>
     <Setter Property="Margin" Value="0"/>
     <Setter Property="Margin" Value="0"/>
     <Setter Property="Padding" Value="16"/>
     <Setter Property="Padding" Value="16"/>
+    <Setter Property="Opacity" Value="0.5"/>
+    <Setter Property="Transitions">
+      <Transitions>
+        <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
+      </Transitions>
+    </Setter>
+  </Style>
+
+  <Style Selector="TabControl.sidebar TabStripItem:pointerover">
+    <Setter Property="Opacity" Value="1"/>
   </Style>
   </Style>
 
 
   <Style Selector="TabControl.sidebar TabStripItem:selected">
   <Style Selector="TabControl.sidebar TabStripItem:selected">
     <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
     <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
+    <Setter Property="Opacity" Value="1"/>
   </Style>
   </Style>
 </Styles>
 </Styles>

+ 40 - 0
samples/RenderTest/ViewModels/AnimationsPageViewModel.cs

@@ -0,0 +1,40 @@
+using System;
+using ReactiveUI;
+using Avalonia.Animation;
+
+namespace RenderTest.ViewModels
+{
+    public class AnimationsPageViewModel : ReactiveObject
+    {
+        private string _playStateText = "Pause all animations";
+
+        public AnimationsPageViewModel()
+        {
+            ToggleGlobalPlayState = ReactiveCommand.Create(()=>TogglePlayState());
+        }
+
+        void TogglePlayState()
+        {
+            switch (Timing.GetGlobalPlayState())
+            {
+                case PlayState.Run:
+                    PlayStateText = "Resume all animations";
+                    Timing.SetGlobalPlayState(PlayState.Pause);
+                    break;
+
+                case PlayState.Pause:
+                    PlayStateText = "Pause all animations";
+                    Timing.SetGlobalPlayState(PlayState.Run);
+                    break;
+            }
+        }
+
+        public string PlayStateText
+        {
+            get { return _playStateText; }
+            set { this.RaiseAndSetIfChanged(ref _playStateText, value); }
+        }
+
+        public ReactiveCommand ToggleGlobalPlayState { get; }
+     }
+}

+ 1 - 1
samples/RenderTest/ViewModels/MainWindowViewModel.cs

@@ -5,7 +5,7 @@ namespace RenderTest.ViewModels
 {
 {
     public class MainWindowViewModel : ReactiveObject
     public class MainWindowViewModel : ReactiveObject
     {
     {
-        private bool drawDirtyRects = true;
+        private bool drawDirtyRects = false;
         private bool drawFps = true;
         private bool drawFps = true;
 
 
         public MainWindowViewModel()
         public MainWindowViewModel()

+ 72 - 20
src/Avalonia.Animation/Animatable.cs

@@ -3,6 +3,13 @@
 
 
 using System.Linq;
 using System.Linq;
 using Avalonia.Data;
 using Avalonia.Data;
+using System;
+using System.Reactive.Linq;
+using Avalonia.Collections;
+using Avalonia.Animation;
+using System.Collections.Generic;
+using System.Threading;
+using System.Collections.Concurrent;
 
 
 namespace Avalonia.Animation
 namespace Avalonia.Animation
 {
 {
@@ -12,45 +19,90 @@ namespace Avalonia.Animation
     public class Animatable : AvaloniaObject
     public class Animatable : AvaloniaObject
     {
     {
         /// <summary>
         /// <summary>
-        /// The property transitions for the control.
+        /// Initializes this <see cref="Animatable"/> object.
         /// </summary>
         /// </summary>
-        private PropertyTransitions _propertyTransitions;
+        public Animatable()
+        {
+            Transitions = new Transitions();
+            AnimatableTimer = Timing.AnimationStateTimer
+                                .Select(p =>
+                                {
+                                    if (this._playState == PlayState.Pause)
+                                    {
+                                        return PlayState.Pause;
+                                    }
+                                    else return p;
+                                })
+                                .Publish()
+                                .RefCount();
+        }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the property transitions for the control.
+        /// The specific animations timer for this control.
         /// </summary>
         /// </summary>
-        /// <value>
-        /// The property transitions for the control.
-        /// </value>
-        public PropertyTransitions PropertyTransitions
+        /// <returns></returns>
+        public IObservable<PlayState> AnimatableTimer;
+
+        /// <summary>
+        /// Defines the <see cref="PlayState"/> property.
+        /// </summary>
+        public static readonly DirectProperty<Animatable, PlayState> PlayStateProperty =
+            AvaloniaProperty.RegisterDirect<Animatable, PlayState>(
+                nameof(PlayState),
+                o => o.PlayState,
+                (o, v) => o.PlayState = v);
+
+        private PlayState _playState = PlayState.Run;
+
+        /// <summary>
+        /// Gets or sets the state of the animation for this
+        /// control.
+        /// </summary>
+        public PlayState PlayState
         {
         {
-            get
-            {
-                return _propertyTransitions ?? (_propertyTransitions = new PropertyTransitions());
-            }
+            get { return _playState; }
+            set { SetAndRaise(PlayStateProperty, ref _playState, value); }
 
 
-            set
-            {
-                _propertyTransitions = value;
-            }
         }
         }
 
 
+
         /// <summary>
         /// <summary>
-        /// Reacts to a change in a <see cref="AvaloniaProperty"/> value in order to animate the
-        /// change if a <see cref="PropertyTransition"/> is set for the property..
+        /// Defines the <see cref="Transitions"/> property.
+        /// </summary>
+        public static readonly DirectProperty<Animatable, IEnumerable<ITransition>> TransitionsProperty =
+            AvaloniaProperty.RegisterDirect<Animatable, IEnumerable<ITransition>>(
+                nameof(Transitions),
+                o => o.Transitions,
+                (o, v) => o.Transitions = v);
+
+        private IEnumerable<ITransition> _transitions = new AvaloniaList<ITransition>();
+
+        /// <summary>
+        /// Gets or sets the property transitions for the control.
+        /// </summary>
+        public IEnumerable<ITransition> Transitions
+        {
+            get { return _transitions; }
+            set { SetAndRaise(TransitionsProperty, ref _transitions, value); }
+        }
+
+        /// <summary>
+        /// Reacts to a change in a <see cref="AvaloniaProperty"/> value in 
+        /// order to animate the change if a <see cref="ITransition"/> is set for the property.
         /// </summary>
         /// </summary>
         /// <param name="e">The event args.</param>
         /// <param name="e">The event args.</param>
         protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
         protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
         {
         {
-            if (e.Priority != BindingPriority.Animation && _propertyTransitions != null)
+            if (e.Priority != BindingPriority.Animation && Transitions != null)
             {
             {
-                var match = _propertyTransitions.FirstOrDefault(x => x.Property == e.Property);
+                var match = Transitions.FirstOrDefault(x => x.Property == e.Property);
 
 
                 if (match != null)
                 if (match != null)
                 {
                 {
-                    Animate.Property(this, e.Property, e.OldValue, e.NewValue, match.Easing, match.Duration);
+                    match.Apply(this, e.OldValue, e.NewValue);
                 }
                 }
             }
             }
         }
         }
+
     }
     }
 }
 }

+ 0 - 134
src/Avalonia.Animation/Animate.cs

@@ -1,134 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Diagnostics;
-using System.Linq;
-using System.Reactive.Linq;
-using Avalonia.Data;
-using Avalonia.Threading;
-
-namespace Avalonia.Animation
-{
-    /// <summary>
-    /// Utilities for creating animations.
-    /// </summary>
-    public static class Animate
-    {
-        /// <summary>
-        /// The number of frames per second.
-        /// </summary>
-        public const int FramesPerSecond = 60;
-
-        /// <summary>
-        /// The time span of each frame.
-        /// </summary>
-        private static readonly TimeSpan Tick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
-
-        /// <summary>
-        /// Initializes static members of the <see cref="Animate"/> class.
-        /// </summary>
-        static Animate()
-        {
-            Stopwatch = new Stopwatch();
-            Stopwatch.Start();
-            Timer = Observable.Interval(Tick, AvaloniaScheduler.Instance)
-                .Select(_ => Stopwatch.Elapsed)
-                .Publish()
-                .RefCount();
-        }
-
-        /// <summary>
-        /// The stopwatch used to track time.
-        /// </summary>
-        /// <value>
-        /// The stopwatch used to track time.
-        /// </value>
-        public static Stopwatch Stopwatch
-        {
-            get; }
-
-        /// <summary>
-        /// Gets the animation timer.
-        /// </summary>
-        /// <remarks>
-        /// The animation timer ticks <see cref="FramesPerSecond"/> times per second. The
-        /// parameter passed to a subsciber is the time span since the animation system was
-        /// initialized.
-        /// </remarks>
-        /// <value>
-        /// The animation timer.
-        /// </value>
-        public static IObservable<TimeSpan> Timer
-        {
-            get; }
-
-        /// <summary>
-        /// Gets a timer that fires every frame for the specified duration.
-        /// </summary>
-        /// <param name="duration">The duration of the animation.</param>
-        /// <returns>
-        /// An observable that notifies the subscriber of the progress along the animation.
-        /// </returns>
-        /// <remarks>
-        /// The parameter passed to the subscriber is the progress along the animation, with
-        /// 0 being the start and 1 being the end. The observable is guaranteed to fire 0
-        /// immediately on subscribe and 1 at the end of the duration.
-        /// </remarks>
-        public static IObservable<double> GetTimer(TimeSpan duration)
-        {
-            var startTime = Stopwatch.Elapsed.Ticks;
-            var endTime = startTime + duration.Ticks;
-            return Timer
-                .TakeWhile(x => x.Ticks < endTime)
-                .Select(x => (x.Ticks - startTime) / (double)duration.Ticks)
-                .StartWith(0.0)
-                .Concat(Observable.Return(1.0));
-        }
-
-        /// <summary>
-        /// Animates a <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <param name="target">The target object.</param>
-        /// <param name="property">The target property.</param>
-        /// <param name="start">The value of the property at the start of the animation.</param>
-        /// <param name="finish">The value of the property at the end of the animation.</param>
-        /// <param name="easing">The easing function to use.</param>
-        /// <param name="duration">The duration of the animation.</param>
-        /// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns>
-        public static Animation Property(
-            IAvaloniaObject target,
-            AvaloniaProperty property,
-            object start,
-            object finish,
-            IEasing easing,
-            TimeSpan duration)
-        {
-            var o = GetTimer(duration).Select(progress => easing.Ease(progress, start, finish));
-            return new Animation(o, target.Bind(property, o, BindingPriority.Animation));
-        }
-
-        /// <summary>
-        /// Animates a <see cref="AvaloniaProperty"/>.
-        /// </summary>
-        /// <typeparam name="T">The property type.</typeparam>
-        /// <param name="target">The target object.</param>
-        /// <param name="property">The target property.</param>
-        /// <param name="start">The value of the property at the start of the animation.</param>
-        /// <param name="finish">The value of the property at the end of the animation.</param>
-        /// <param name="easing">The easing function to use.</param>
-        /// <param name="duration">The duration of the animation.</param>
-        /// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns>
-        public static Animation<T> Property<T>(
-            IAvaloniaObject target,
-            AvaloniaProperty<T> property,
-            T start,
-            T finish,
-            IEasing<T> easing,
-            TimeSpan duration)
-        {
-            var o = GetTimer(duration).Select(progress => easing.Ease(progress, start, finish));
-            return new Animation<T>(o, target.Bind(property, o, BindingPriority.Animation));
-        }
-    }
-}

+ 113 - 23
src/Avalonia.Animation/Animation.cs

@@ -1,34 +1,118 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
+using Avalonia.Animation.Easings;
+using Avalonia.Animation;
+using Avalonia.Collections;
+using Avalonia.Metadata;
 using System;
 using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Reflection;
+using System.Linq;
 
 
 namespace Avalonia.Animation
 namespace Avalonia.Animation
 {
 {
     /// <summary>
     /// <summary>
     /// Tracks the progress of an animation.
     /// Tracks the progress of an animation.
     /// </summary>
     /// </summary>
-    public class Animation : IObservable<object>, IDisposable
+    public class Animation : AvaloniaList<KeyFrame>, IDisposable, IAnimation
     {
     {
+
+        private bool _isChildrenChanged = false;
+        private List<IDisposable> _subscription = new List<IDisposable>();
+        public AvaloniaList<IAnimator> _animators { get; set; } = new AvaloniaList<IAnimator>();
+
         /// <summary>
         /// <summary>
-        /// The animation being tracked.
+        /// Run time of this animation.
         /// </summary>
         /// </summary>
-        private readonly IObservable<object> _inner;
+        public TimeSpan Duration { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// The disposable used to cancel the animation.
+        /// Delay time for this animation.
         /// </summary>
         /// </summary>
-        private readonly IDisposable _subscription;
+        public TimeSpan Delay { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="Animation"/> class.
+        /// The repeat count for this animation.
         /// </summary>
         /// </summary>
-        /// <param name="inner">The animation observable being tracked.</param>
-        /// <param name="subscription">A disposable used to cancel the animation.</param>
-        public Animation(IObservable<object> inner, IDisposable subscription)
+        public RepeatCount RepeatCount { get; set; }
+
+        /// <summary>
+        /// The playback direction for this animation.
+        /// </summary>
+        public PlaybackDirection PlaybackDirection { get; set; }
+
+        /// <summary>
+        /// The value fill mode for this animation.
+        /// </summary>
+        public FillMode FillMode { get; set; } 
+
+        /// <summary>
+        /// Easing function to be used.
+        /// </summary> 
+        public Easing Easing { get; set; } = new LinearEasing();
+
+        public Animation()
+        {
+            this.CollectionChanged += delegate { _isChildrenChanged = true; };
+        }
+ 
+        private void InterpretKeyframes()
         {
         {
-            _inner = inner;
-            _subscription = subscription;
+            var handlerList = new List<(Type, AvaloniaProperty)>();
+            var kfList = new List<AnimatorKeyFrame>();
+
+            foreach (var keyframe in this)
+            {
+                foreach (var setter in keyframe)
+                {
+                    var custAttr = setter.GetType()
+                                         .GetCustomAttributes()
+                                         .Where(p => p.GetType() == typeof(AnimatorAttribute));
+
+                    if (!custAttr.Any())
+                        throw new InvalidProgramException($"Type {setter.GetType()} doesn't have Animator attribute.");
+
+                    var handler = ((AnimatorAttribute)custAttr.First()).HandlerType;
+                    if (!handlerList.Contains((handler, setter.Property)))
+                        handlerList.Add((handler, setter.Property));
+
+                    var newKF = new AnimatorKeyFrame()
+                    {
+                        Handler = handler,
+                        Property = setter.Property,
+                        Cue = keyframe.Cue,
+                        KeyTime = keyframe.KeyTime,
+                        timeSpanSet = keyframe.timeSpanSet,
+                        cueSet = keyframe.cueSet,
+                        Value = setter.Value
+                    };
+
+                    kfList.Add(newKF);
+                }
+            }
+
+            var newAnimatorInstances = new List<(Type handler, AvaloniaProperty prop, IAnimator inst)>();
+
+            foreach (var handler in handlerList)
+            {
+                var newInstance = (IAnimator)Activator.CreateInstance(handler.Item1);
+                newInstance.Property = handler.Item2;
+                newAnimatorInstances.Add((handler.Item1, handler.Item2, newInstance));
+            }
+
+            foreach (var kf in kfList)
+            {
+                var parent = newAnimatorInstances.Where(p => p.handler == kf.Handler &&
+                                                             p.prop == kf.Property)
+                                                 .First();
+                parent.inst.Add(kf);
+            }
+
+            foreach(var instance in newAnimatorInstances)
+                _animators.Add(instance.inst);
+
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -36,20 +120,26 @@ namespace Avalonia.Animation
         /// </summary>
         /// </summary>
         public void Dispose()
         public void Dispose()
         {
         {
-            _subscription.Dispose();
+            foreach (var sub in _subscription)
+            {
+                sub.Dispose();
+            }
         }
         }
 
 
-        /// <summary>
-        /// Notifies the provider that an observer is to receive notifications.
-        /// </summary>
-        /// <param name="observer">The observer.</param>
-        /// <returns>
-        /// A reference to an interface that allows observers to stop receiving notifications
-        /// before the provider has finished sending them.
-        /// </returns>
-        public IDisposable Subscribe(IObserver<object> observer)
+        /// <inheritdocs/>
+        public IDisposable Apply(Animatable control, IObservable<bool> matchObs)
         {
         {
-            return _inner.Subscribe(observer);
+            if (_isChildrenChanged)
+            {
+                InterpretKeyframes();
+                _isChildrenChanged = false;
+            }
+
+            foreach (IAnimator keyframes in _animators)
+            {
+                _subscription.Add(keyframes.Apply(this, control, matchObs));
+            }
+            return this;
         }
         }
     }
     }
-}
+}

+ 0 - 45
src/Avalonia.Animation/AnimationExtensions.cs

@@ -1,45 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-
-namespace Avalonia.Animation
-{
-    /// <summary>
-    /// Defines animation extension methods.
-    /// </summary>
-    public static class AnimationExtensions
-    {
-        /// <summary>
-        /// Returns a new <see cref="PropertyTransition"/> for the specified
-        /// <see cref="AvaloniaProperty"/> using linear easing.
-        /// </summary>
-        /// <typeparam name="T">The type of the <see cref="AvaloniaProperty"/>.</typeparam>
-        /// <param name="property">The property to animate.</param>
-        /// <param name="milliseconds">The animation duration in milliseconds.</param>
-        /// <returns>
-        /// A <see cref="PropertyTransition"/> that can be added to the
-        /// <see cref="Animatable.PropertyTransitions"/> collection.
-        /// </returns>
-        public static PropertyTransition Transition<T>(this AvaloniaProperty<T> property, int milliseconds)
-        {
-            return Transition(property, TimeSpan.FromMilliseconds(milliseconds));
-        }
-
-        /// <summary>
-        /// Returns a new <see cref="PropertyTransition"/> for the specified
-        /// <see cref="AvaloniaProperty"/> using linear easing.
-        /// </summary>
-        /// <typeparam name="T">The type of the <see cref="AvaloniaProperty"/>.</typeparam>
-        /// <param name="property">The property to animate.</param>
-        /// <param name="duration">The animation duration.</param>
-        /// <returns>
-        /// A <see cref="PropertyTransition"/> that can be added to the
-        /// <see cref="Animatable.PropertyTransitions"/> collection.
-        /// </returns>
-        public static PropertyTransition Transition<T>(this AvaloniaProperty<T> property, TimeSpan duration)
-        {
-            return new PropertyTransition(property, duration, LinearEasing.For<T>());
-        }
-    }
-}

+ 17 - 0
src/Avalonia.Animation/AnimationSetter.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Diagnostics;
+using Avalonia.Animation.Utils;
+using Avalonia.Data;
+
+namespace Avalonia.Animation
+{
+    public abstract class AnimationSetter : IAnimationSetter
+    {
+        public AvaloniaProperty Property { get; set; }
+        public object Value { get; set; }
+    }
+}

+ 0 - 56
src/Avalonia.Animation/Animation`1.cs

@@ -1,56 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-
-namespace Avalonia.Animation
-{
-    /// <summary>
-    /// Tracks the progress of an animation.
-    /// </summary>
-    /// <typeparam name="T">The type of the value being animated./</typeparam>
-    public class Animation<T> : IObservable<T>, IDisposable
-    {
-        /// <summary>
-        /// The animation being tracked.
-        /// </summary>
-        private readonly IObservable<T> _inner;
-
-        /// <summary>
-        /// The disposable used to cancel the animation.
-        /// </summary>
-        private readonly IDisposable _subscription;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="Animation{T}"/> class.
-        /// </summary>
-        /// <param name="inner">The animation observable being tracked.</param>
-        /// <param name="subscription">A disposable used to cancel the animation.</param>
-        public Animation(IObservable<T> inner, IDisposable subscription)
-        {
-            _inner = inner;
-            _subscription = subscription;
-        }
-
-        /// <summary>
-        /// Cancels the animation.
-        /// </summary>
-        public void Dispose()
-        {
-            _subscription.Dispose();
-        }
-
-        /// <summary>
-        /// Notifies the provider that an observer is to receive notifications.
-        /// </summary>
-        /// <param name="observer">The observer.</param>
-        /// <returns>
-        /// A reference to an interface that allows observers to stop receiving notifications
-        /// before the provider has finished sending them.
-        /// </returns>
-        public IDisposable Subscribe(IObserver<T> observer)
-        {
-            return _inner.Subscribe(observer);
-        }
-    }
-}

+ 18 - 0
src/Avalonia.Animation/AnimatorAttribute.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Attribute for <see cref="IAnimationSetter"/> objects
+    /// that maps the setter to it's <see cref="Animator{T}"/>.
+    /// </summary>
+    public class AnimatorAttribute : Attribute
+    {
+        public Type HandlerType;
+
+        public AnimatorAttribute(Type handler)
+        {
+            this.HandlerType = handler;
+        }
+    }
+}

+ 23 - 0
src/Avalonia.Animation/AnimatorKeyFrame.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.ComponentModel;
+using Avalonia.Metadata;
+using Avalonia.Collections;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Defines a KeyFrame that is used for
+    /// <see cref="Animators"/> objects.
+    /// </summary>
+    public class AnimatorKeyFrame
+    {
+        public Type Handler;
+        public Cue Cue;
+        public TimeSpan KeyTime;
+        internal bool timeSpanSet, cueSet;
+        public AvaloniaProperty Property;
+        public object Value;
+    }
+}

+ 256 - 0
src/Avalonia.Animation/AnimatorStateMachine`1.cs

@@ -0,0 +1,256 @@
+using System;
+using System.Linq;
+using Avalonia.Data;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Provides statefulness for an iteration of a keyframe animation.
+    /// </summary>
+    internal class AnimatorStateMachine<T> : IObservable<object>, IDisposable
+    {
+        object _lastInterpValue;
+        object _firstKFValue;
+
+        private ulong _delayTotalFrameCount;
+        private ulong _durationTotalFrameCount;
+        private ulong _delayFrameCount;
+        private ulong _durationFrameCount;
+        private ulong _repeatCount;
+        private ulong _currentIteration;
+
+        private bool _isLooping;
+        private bool _isRepeating;
+        private bool _isReversed;
+        private bool _checkLoopAndRepeat;
+        private bool _gotFirstKFValue;
+
+        private FillMode _fillMode;
+        private PlaybackDirection _animationDirection;
+        private KeyFramesStates _currentState;
+        private KeyFramesStates _savedState;
+        private Animator<T> _parent;
+        private Animation _targetAnimation;
+        private Animatable _targetControl;
+        private T _neutralValue;
+        internal bool _unsubscribe = false;
+        private IObserver<object> _targetObserver;
+
+        [Flags]
+        private enum KeyFramesStates
+        {
+            Initialize,
+            DoDelay,
+            DoRun,
+            RunForwards,
+            RunBackwards,
+            RunApplyValue,
+            RunComplete,
+            Pause,
+            Stop,
+            Disposed
+        }
+
+        public void Initialize(Animation animation, Animatable control, Animator<T> keyframes)
+        {
+            _parent = keyframes;
+            _targetAnimation = animation;
+            _targetControl = control;
+            _neutralValue = (T)_targetControl.GetValue(_parent.Property);
+
+            _delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks);
+            _durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks);
+
+            switch (animation.RepeatCount.RepeatType)
+            {
+                case RepeatType.Loop:
+                    _isLooping = true;
+                    _checkLoopAndRepeat = true;
+                    break;
+                case RepeatType.Repeat:
+                    _isRepeating = true;
+                    _checkLoopAndRepeat = true;
+                    _repeatCount = animation.RepeatCount.Value;
+                    break;
+            }
+
+            _isReversed = (animation.PlaybackDirection & PlaybackDirection.Reverse) != 0;
+            _animationDirection = _targetAnimation.PlaybackDirection;
+            _fillMode = _targetAnimation.FillMode;
+
+            if (_durationTotalFrameCount > 0)
+                _currentState = KeyFramesStates.DoDelay;
+            else
+                _currentState = KeyFramesStates.DoRun;
+        }
+
+        public void Step(PlayState _playState, Func<double, T, T> Interpolator)
+        {
+            try
+            {
+                InternalStep(_playState, Interpolator);
+            }
+            catch (Exception e)
+            {
+                _targetObserver?.OnError(e);
+            }
+        }
+
+        private void InternalStep(PlayState _playState, Func<double, T, T> Interpolator)
+        {
+            if (!_gotFirstKFValue)
+            {
+                _firstKFValue = _parent.First().Value;
+                _gotFirstKFValue = true;
+            }
+
+            if (_currentState == KeyFramesStates.Disposed)
+                throw new InvalidProgramException("This KeyFrames Animation is already disposed.");
+
+            if (_playState == PlayState.Stop)
+                _currentState = KeyFramesStates.Stop;
+
+            // Save state and pause the machine
+            if (_playState == PlayState.Pause && _currentState != KeyFramesStates.Pause)
+            {
+                _savedState = _currentState;
+                _currentState = KeyFramesStates.Pause;
+            }
+
+            // Resume the previous state
+            if (_playState != PlayState.Pause && _currentState == KeyFramesStates.Pause)
+                _currentState = _savedState;
+
+            double _tempDuration = 0d, _easedTime;
+
+        checkstate:
+            switch (_currentState)
+            {
+                case KeyFramesStates.DoDelay:
+
+                    if (_fillMode == FillMode.Backward
+                     || _fillMode == FillMode.Both)
+                    {
+                        if (_currentIteration == 0)
+                        {
+                            _targetObserver.OnNext(_firstKFValue);
+                        }
+                        else
+                        {
+                            _targetObserver.OnNext(_lastInterpValue);
+                        }
+                    }
+
+                    if (_delayFrameCount > _delayTotalFrameCount)
+                    {
+                        _currentState = KeyFramesStates.DoRun;
+                        goto checkstate;
+                    }
+                    _delayFrameCount++;
+                    break;
+
+                case KeyFramesStates.DoRun:
+
+                    if (_isReversed)
+                        _currentState = KeyFramesStates.RunBackwards;
+                    else
+                        _currentState = KeyFramesStates.RunForwards;
+
+                    goto checkstate;
+
+                case KeyFramesStates.RunForwards:
+
+                    if (_durationFrameCount > _durationTotalFrameCount)
+                    {
+                        _currentState = KeyFramesStates.RunComplete;
+                        goto checkstate;
+                    }
+
+                    _tempDuration = (double)_durationFrameCount / _durationTotalFrameCount;
+                    _currentState = KeyFramesStates.RunApplyValue;
+
+                    goto checkstate;
+
+                case KeyFramesStates.RunBackwards:
+
+                    if (_durationFrameCount > _durationTotalFrameCount)
+                    {
+                        _currentState = KeyFramesStates.RunComplete;
+                        goto checkstate;
+                    }
+
+                    _tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount;
+                    _currentState = KeyFramesStates.RunApplyValue;
+
+                    goto checkstate;
+
+                case KeyFramesStates.RunApplyValue:
+
+                    _easedTime = _targetAnimation.Easing.Ease(_tempDuration);
+
+                    _durationFrameCount++;
+                    _lastInterpValue = Interpolator(_easedTime, _neutralValue);
+                    _targetObserver.OnNext(_lastInterpValue);
+                    _currentState = KeyFramesStates.DoRun;
+
+                    break;
+
+                case KeyFramesStates.RunComplete:
+
+                    if (_checkLoopAndRepeat)
+                    {
+                        _delayFrameCount = 0;
+                        _durationFrameCount = 0;
+
+                        if (_isLooping)
+                        {
+                            _currentState = KeyFramesStates.DoRun;
+                        }
+                        else if (_isRepeating)
+                        {
+                            if (_currentIteration >= _repeatCount)
+                            {
+                                _currentState = KeyFramesStates.Stop;
+                            }
+                            else
+                            {
+                                _currentState = KeyFramesStates.DoRun;
+                            }
+                            _currentIteration++;
+                        }
+
+                        if (_animationDirection == PlaybackDirection.Alternate
+                         || _animationDirection == PlaybackDirection.AlternateReverse)
+                            _isReversed = !_isReversed;
+
+                        break;
+                    }
+
+                    _currentState = KeyFramesStates.Stop;
+                    goto checkstate;
+
+                case KeyFramesStates.Stop:
+
+                    if (_fillMode == FillMode.Forward
+                     || _fillMode == FillMode.Both)
+                    {
+                        _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
+                    }
+                    _targetObserver.OnCompleted();
+                    break;
+            }
+        }
+
+        public IDisposable Subscribe(IObserver<object> observer)
+        {
+            _targetObserver = observer;
+            return this;
+        }
+
+        public void Dispose()
+        {
+            _unsubscribe = true;
+            _currentState = KeyFramesStates.Disposed;
+        }
+    }
+}

+ 192 - 0
src/Avalonia.Animation/Animator`1.cs

@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Collections;
+using System.ComponentModel;
+using Avalonia.Animation.Utils;
+using System.Reactive.Linq;
+using System.Linq;
+using Avalonia.Data;
+using System.Reactive.Disposables;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Base class for KeyFrames objects
+    /// </summary>
+    public abstract class Animator<T> : AvaloniaList<AnimatorKeyFrame>, IAnimator
+    {
+        /// <summary>
+        /// List of type-converted keyframes.
+        /// </summary>
+        private Dictionary<double, (T, bool isNeutral)> _convertedKeyframes = new Dictionary<double, (T, bool)>();
+
+        private bool _isVerfifiedAndConverted;
+
+        /// <summary>
+        /// Gets or sets the target property for the keyframe.
+        /// </summary>
+        public AvaloniaProperty Property { get; set; }
+
+        public Animator()
+        {
+            // Invalidate keyframes when changed.
+            this.CollectionChanged += delegate { _isVerfifiedAndConverted = false; };
+        }
+
+        /// <inheritdoc/>
+        public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
+        {
+            if (!_isVerfifiedAndConverted)
+                VerifyConvertKeyFrames(animation, typeof(T));
+
+            return obsMatch
+                .Where(p => p == true)
+                // Ignore triggers when global timers are paused.
+                .Where(p => Timing.GetGlobalPlayState() != PlayState.Pause)
+                .Subscribe(_ =>
+                {
+                    var timerObs = RunKeyFrames(animation, control);
+                });
+        }
+
+        /// <summary>
+        /// Get the nearest pair of cue-time ordered keyframes 
+        /// according to the given time parameter that is relative to the
+        /// total animation time and the normalized intra-keyframe pair time 
+        /// (i.e., the normalized time between the selected keyframes, relative to the
+        /// time parameter).
+        /// </summary>
+        /// <param name="t">The time parameter, relative to the total animation time</param>
+        protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t)
+        {
+            KeyValuePair<double, (T, bool)> firstCue, lastCue;
+            int kvCount = _convertedKeyframes.Count();
+            if (kvCount > 2)
+            {
+                if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0)
+                {
+                    firstCue = _convertedKeyframes.First();
+                    lastCue = _convertedKeyframes.Skip(1).First();
+                }
+                else if (DoubleUtils.AboutEqual(t, 1.0) || t > 1.0)
+                {
+                    firstCue = _convertedKeyframes.Skip(kvCount - 2).First();
+                    lastCue = _convertedKeyframes.Last();
+                }
+                else
+                {
+                    firstCue = _convertedKeyframes.Where(j => j.Key <= t).Last();
+                    lastCue = _convertedKeyframes.Where(j => j.Key >= t).First();
+                }
+            }
+            else
+            {
+                firstCue = _convertedKeyframes.First();
+                lastCue = _convertedKeyframes.Last();
+            }
+
+            double t0 = firstCue.Key;
+            double t1 = lastCue.Key;
+            var intraframeTime = (t - t0) / (t1 - t0);
+            return (intraframeTime, new KeyFramePair<T>(firstCue, lastCue));
+        }
+
+
+        /// <summary>
+        /// Runs the KeyFrames Animation.
+        /// </summary>
+        private IDisposable RunKeyFrames(Animation animation, Animatable control)
+        {
+            var _kfStateMach = new AnimatorStateMachine<T>();
+            _kfStateMach.Initialize(animation, control, this);
+
+            Timing.AnimationStateTimer
+                        .TakeWhile(_ => !_kfStateMach._unsubscribe)
+                        .Subscribe(p =>
+                        {
+                            _kfStateMach.Step(p, DoInterpolation);
+                        });
+
+            return control.Bind(Property, _kfStateMach, BindingPriority.Animation);
+        }
+
+        /// <summary>
+        /// Interpolates a value given the desired time.
+        /// </summary>
+        protected abstract T DoInterpolation(double time, T neutralValue);
+
+        /// <summary>
+        /// Verifies and converts keyframe values according to this class's target type.
+        /// </summary>
+        private void VerifyConvertKeyFrames(Animation animation, Type type)
+        {
+            var typeConv = TypeDescriptor.GetConverter(type);
+
+            foreach (AnimatorKeyFrame k in this)
+            {
+                if (k.Value == null)
+                {
+                    throw new ArgumentNullException($"KeyFrame value can't be null.");
+                }
+                if (!typeConv.CanConvertTo(k.Value.GetType()))
+                {
+                    throw new InvalidCastException($"KeyFrame value doesnt match property type.");
+                }
+
+                T convertedValue = (T)typeConv.ConvertTo(k.Value, type);
+
+                Cue _normalizedCue = k.Cue;
+
+                if (k.timeSpanSet)
+                {
+                    _normalizedCue = new Cue(k.KeyTime.Ticks / animation.Duration.Ticks);
+                }
+
+                _convertedKeyframes.Add(_normalizedCue.CueValue, (convertedValue, false));
+            }
+
+            SortKeyFrameCues(_convertedKeyframes);
+            _isVerfifiedAndConverted = true;
+
+        }
+
+        private void SortKeyFrameCues(Dictionary<double, (T, bool)> convertedValues)
+        {
+            bool hasStartKey, hasEndKey;
+            hasStartKey = hasEndKey = false;
+
+            // Make start and end keyframe mandatory.
+            foreach (var converted in _convertedKeyframes.Keys)
+            {
+                if (DoubleUtils.AboutEqual(converted, 0.0))
+                {
+                    hasStartKey = true;
+                }
+                else if (DoubleUtils.AboutEqual(converted, 1.0))
+                {
+                    hasEndKey = true;
+                }
+            }
+
+            if (!hasStartKey || !hasEndKey)
+                AddNeutralKeyFrames(hasStartKey, hasEndKey, _convertedKeyframes);
+
+            _convertedKeyframes = _convertedKeyframes.OrderBy(p => p.Key)
+                                                     .ToDictionary((k) => k.Key, (v) => v.Value);
+        }
+
+        private void AddNeutralKeyFrames(bool hasStartKey, bool hasEndKey, Dictionary<double, (T, bool)> convertedKeyframes)
+        {
+            if (!hasStartKey)
+            {
+                convertedKeyframes.Add(0.0d, (default(T), true));
+            }
+
+            if (!hasEndKey)
+            {
+                convertedKeyframes.Add(1.0d, (default(T), true));
+            }
+        }
+    }
+}

+ 88 - 0
src/Avalonia.Animation/Cue.cs

@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// A Cue object for <see cref="KeyFrame"/>. 
+    /// </summary>
+    [TypeConverter(typeof(CueTypeConverter))]
+    public struct Cue : IEquatable<Cue>, IEquatable<double>
+    {
+        /// <summary>
+        /// The normalized percent value, ranging from 0.0 to 1.0
+        /// </summary>
+        public double CueValue { get; }
+
+        /// <summary>
+        /// Sets a new <see cref="Cue"/> object.
+        /// </summary>
+        /// <param name="value"></param>
+        public Cue(double value)
+        {
+            if (value <= 1 && value >= 0)
+                CueValue = value;
+            else
+                throw new ArgumentException($"This cue object's value should be within or equal to 0.0 and 1.0");
+        }
+
+        /// <summary>
+        /// Parses a string to a <see cref="Cue"/> object.
+        /// </summary>
+        public static object Parse(string value, CultureInfo culture)
+        {
+            string v = value;
+
+            if (value.EndsWith("%"))
+            {
+                v = v.TrimEnd('%');
+            }
+
+            if (double.TryParse(v, NumberStyles.Float, culture, out double res))
+            {
+                return new Cue(res / 100d);
+            }
+            else
+            {
+                throw new FormatException($"Invalid Cue string \"{value}\"");
+            }
+        }
+
+        /// <summary>
+        /// Checks for equality between two <see cref="Cue"/>s.
+        /// </summary>
+        /// <param name="other">The second cue.</param>
+        public bool Equals(Cue other)
+        {
+            return CueValue == other.CueValue;
+        }
+
+        /// <summary>
+        /// Checks for equality between a <see cref="Cue"/>
+        /// and a <see cref="double"/> value.
+        /// </summary>
+        /// <param name="other"></param>
+        /// <returns></returns>
+        public bool Equals(double other)
+        {
+            return CueValue == other;
+        }
+    }
+
+    public class CueTypeConverter : TypeConverter 
+    {
+        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        {
+            return Cue.Parse((string)value, culture);
+        }
+    }
+
+}

+ 41 - 0
src/Avalonia.Animation/DoubleAnimator.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Diagnostics;
+using Avalonia.Animation.Utils;
+using Avalonia.Data;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Animator that handles <see cref="double"/> properties.
+    /// </summary>
+    public class DoubleAnimator : Animator<double>
+    {
+
+        /// <inheritdocs/>
+        protected override double DoInterpolation(double t, double neutralValue)
+        {
+            var pair = GetKFPairAndIntraKFTime(t);
+            double y0, y1;
+
+            var firstKF = pair.KFPair.FirstKeyFrame;
+            var secondKF = pair.KFPair.SecondKeyFrame;
+
+            if (firstKF.Value.isNeutral)
+                y0 = neutralValue;
+            else
+                y0 = firstKF.Value.TargetValue;
+
+            if (secondKF.Value.isNeutral)
+                y1 = neutralValue;
+            else
+                y1 = secondKF.Value.TargetValue;
+
+            // Do linear parametric interpolation 
+            return y0 + (pair.IntraKFTime) * (y1 - y0);
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Animation/DoubleSetter.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Diagnostics;
+using Avalonia.Animation.Utils;
+using Avalonia.Data;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Setter that handles <see cref="double"/> properties
+    /// in the target.
+    /// </summary>
+    [Animator(typeof(DoubleAnimator))]
+    public class DoubleSetter : AnimationSetter
+    {
+
+    }
+}

+ 23 - 0
src/Avalonia.Animation/DoubleTransition.cs

@@ -0,0 +1,23 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Metadata;
+using System;
+using System.Reactive.Linq;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="double"/> types.
+    /// </summary>  
+    public class DoubleTransition : Transition<double>
+    {
+        /// <inheritdocs/>
+        public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
+        {
+            var delta = newValue - oldValue;
+            return progress
+                .Select(p => Easing.Ease(p) * delta + oldValue);
+        }
+    }
+}

+ 20 - 0
src/Avalonia.Animation/Easing/BackEaseIn.cs

@@ -0,0 +1,20 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using a overshooting cubic function.
+    /// </summary>
+    public class BackEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double p)
+        {
+            return p * (p * p - Math.Sin(p * Math.PI)); 
+        }
+    }
+}

+ 31 - 0
src/Avalonia.Animation/Easing/BackEaseInOut.cs

@@ -0,0 +1,31 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piecewise overshooting cubic function.
+    /// </summary>
+    public class BackEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+
+            if (p < 0.5d)
+            {
+                double f = 2d * p;
+                return 0.5d * f * (f * f - Math.Sin(f * Math.PI));
+            }
+            else
+            {
+                double f = (1d - (2d * p - 1d));
+                return 0.5d * (1d - f * (f * f - Math.Sin(f * Math.PI))) + 0.5d;
+            }
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Animation/Easing/BackEaseOut.cs

@@ -0,0 +1,21 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using a overshooting cubic function.
+    /// </summary>
+    public class BackEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = 1d - progress;
+            return 1 - p * (p * p - Math.Sin(p * Math.PI));
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Animation/Easing/BounceEaseIn.cs

@@ -0,0 +1,21 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+
+using Avalonia.Animation.Utils;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using a simulated bounce function.
+    /// </summary>
+    public class BounceEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return 1 - BounceEaseUtils.Bounce(1 - progress);
+        }
+    }
+}

+ 28 - 0
src/Avalonia.Animation/Easing/BounceEaseInOut.cs

@@ -0,0 +1,28 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Animation.Utils;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piecewise simulated bounce function.
+    /// </summary>
+    public class BounceEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+            if (p < 0.5d)
+            {
+                return 0.5f * (1 - BounceEaseUtils.Bounce(1 - (p * 2)));
+            }
+            else
+            {
+                return 0.5f * BounceEaseUtils.Bounce(p * 2 - 1) + 0.5f;
+            }
+        }
+    }
+}

+ 19 - 0
src/Avalonia.Animation/Easing/BounceEaseOut.cs

@@ -0,0 +1,19 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Avalonia.Animation.Utils;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using a simulated bounce function.
+    /// </summary>
+    public class BounceEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return BounceEaseUtils.Bounce(progress);
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Animation/Easing/CircularEaseIn.cs

@@ -0,0 +1,21 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using the shifted fourth quadrant of
+    /// the unit circle.
+    /// </summary>
+    public class CircularEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double p)
+        {
+            return 1d - Math.Sqrt(1d - p * p);
+        }
+    }
+}

+ 29 - 0
src/Avalonia.Animation/Easing/CircularEaseInOut.cs

@@ -0,0 +1,29 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piecewise unit circle function.
+    /// </summary>
+    public class CircularEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+            if (p < 0.5d)
+            {
+                return 0.5d * (1d - Math.Sqrt(1d - 4d * p * p));
+            }
+            else
+            {
+                double t = 2d * p;
+                return 0.5d * (Math.Sqrt((3d - t) * (t - 1d)) + 1d);
+            }
+        }
+    }
+}

+ 22 - 0
src/Avalonia.Animation/Easing/CircularEaseOut.cs

@@ -0,0 +1,22 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using the shifted second quadrant of
+    /// the unit circle.
+    /// </summary>
+    public class CircularEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+            return Math.Sqrt((2d - p) * p);
+         }
+    }
+}

+ 18 - 0
src/Avalonia.Animation/Easing/CubicEaseIn.cs

@@ -0,0 +1,18 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using a cubic equation.
+    /// </summary>
+    public class CubicEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return progress * progress * progress;
+        }
+    }
+}

+ 28 - 0
src/Avalonia.Animation/Easing/CubicEaseInOut.cs

@@ -0,0 +1,28 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piece-wise cubic equation.
+    /// </summary>
+    public class CubicEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+
+            if (progress < 0.5d)
+            {
+                return 4d * p * p * p;
+            }
+            else
+            {
+                double f = 2d * (p - 1);
+                return 0.5d * f * f * f + 1d;
+            }
+        }
+    }
+}

+ 19 - 0
src/Avalonia.Animation/Easing/CubicEaseOut.cs

@@ -0,0 +1,19 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using a cubic equation.
+    /// </summary>
+    public class CubicEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double f = (progress - 1d);
+            return f * f * f + 1d;
+        }
+    }
+}

+ 56 - 0
src/Avalonia.Animation/Easing/Easing.cs

@@ -0,0 +1,56 @@
+using Avalonia.Collections;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Reflection;
+using System.Linq;
+using System.ComponentModel;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Base class for all Easing classes.
+    /// </summary>
+    [TypeConverter(typeof(EasingTypeConverter))]
+    public abstract class Easing : IEasing
+    {
+        /// <inheritdoc/>
+        public abstract double Ease(double progress);
+
+        static Dictionary<string, Type> _easingTypes;
+
+        static readonly Type s_thisType = typeof(Easing);
+
+        /// <summary>
+        /// Parses a Easing type string.
+        /// </summary>
+        /// <param name="e">The Easing type string.</param>
+        /// <returns>Returns the instance of the parsed type.</returns>
+        public static Easing Parse(string e)
+        {
+            if (_easingTypes == null)
+            {
+                _easingTypes = new Dictionary<string, Type>();
+
+                // Fetch the built-in easings.
+                var derivedTypes = typeof(Easing).Assembly.GetTypes()
+                                      .Where(p => p.Namespace == s_thisType.Namespace)
+                                      .Where(p => p.IsSubclassOf(s_thisType))
+                                      .Select(p => p).ToList();
+
+                foreach (var easingType in derivedTypes)
+                    _easingTypes.Add(easingType.Name, easingType);
+            }
+
+            if (_easingTypes.ContainsKey(e))
+            {
+                var type = _easingTypes[e];
+                return (Easing)Activator.CreateInstance(type);
+            }
+            else
+            {
+                throw new FormatException($"Easing \"{e}\" was not found in {s_thisType.Namespace} namespace.");
+            }
+        }
+    }
+}

+ 23 - 0
src/Avalonia.Animation/Easing/EasingTypeConverter.cs

@@ -0,0 +1,23 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Animation.Easings;
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Avalonia.Animation.Easings
+{
+    public class EasingTypeConverter : TypeConverter
+    {
+        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        {
+            return Easing.Parse((string)value);
+        }
+    }
+}

+ 22 - 0
src/Avalonia.Animation/Easing/ElasticEaseIn.cs

@@ -0,0 +1,22 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Animation.Utils;
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using a damped sine function.
+    /// </summary>
+    public class ElasticEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+            return Math.Sin(13d * EasingUtils.HALFPI * p) * Math.Pow(2d, 10d * (p - 1));            
+        }
+    }
+}

+ 31 - 0
src/Avalonia.Animation/Easing/ElasticEaseInOut.cs

@@ -0,0 +1,31 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia.Animation.Utils;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piecewise damped sine function.
+    /// </summary>
+    public class ElasticEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+
+            if (p < 0.5d)
+            {
+                double t = 2d * p;
+                return 0.5d * Math.Sin(13d * EasingUtils.HALFPI * t) * Math.Pow(2d, 10d * (t - 1d));
+            }
+            else
+            {
+                return 0.5d * (Math.Sin(-13d * EasingUtils.HALFPI * ((2d * p - 1d) + 1d)) * Math.Pow(2d, -10d * (2d * p - 1d)) + 2d);
+            }
+        }
+    }
+}

+ 22 - 0
src/Avalonia.Animation/Easing/ElasticEaseOut.cs

@@ -0,0 +1,22 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Animation.Utils;
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using a damped sine function.
+    /// </summary>
+    public class ElasticEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+            return Math.Sin(-13d * EasingUtils.HALFPI * (p + 1)) * Math.Pow(2d, -10d * p) + 1d;
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Animation/Easing/ExponentialEaseIn.cs

@@ -0,0 +1,21 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using a exponential function.
+    /// </summary>
+    public class ExponentialEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+            return (p == 0.0d) ? p : Math.Pow(2d, 10d * (p - 1d));
+        }
+    }
+}

+ 29 - 0
src/Avalonia.Animation/Easing/ExponentialEaseInOut.cs

@@ -0,0 +1,29 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piecewise exponential function.
+    /// </summary>
+    public class ExponentialEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+
+            if (p < 0.5d)
+            {
+                return 0.5d * Math.Pow(2d, 20d * p - 10d);
+            }
+            else
+            {
+                return -0.5d * Math.Pow(2d, -20d * p + 10d) + 1d;
+            }
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Animation/Easing/ExponentialEaseOut.cs

@@ -0,0 +1,21 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using a exponential function.
+    /// </summary>
+    public class ExponentialEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+            return (p == 1.0d) ? p : 1d - Math.Pow(2d, -10d * p);
+        }
+    }
+}

+ 17 - 0
src/Avalonia.Animation/Easing/LinearEasing.cs

@@ -0,0 +1,17 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Linearly eases a <see cref="double"/> value.
+    /// </summary>
+    public class LinearEasing : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return progress;
+        }
+    }
+}

+ 18 - 0
src/Avalonia.Animation/Easing/QuadraticEaseIn.cs

@@ -0,0 +1,18 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using a quadratic function.
+    /// </summary>
+    public class QuadraticEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return progress * progress;
+        }
+    }
+}

+ 27 - 0
src/Avalonia.Animation/Easing/QuadraticEaseInOut.cs

@@ -0,0 +1,27 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piece-wise quadratic function.
+    /// </summary>
+    public class QuadraticEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+
+            if (progress < 0.5d)
+            {
+                return 2d * p * p;
+            }
+            else
+            {
+                return p * (-2d * p + 4d) - 1d;
+            }           
+        }
+    }
+}

+ 18 - 0
src/Avalonia.Animation/Easing/QuadraticEaseOut.cs

@@ -0,0 +1,18 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using a quadratic function.
+    /// </summary>
+    public class QuadraticEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return -(progress * (progress - 2d));
+        }
+    }
+}

+ 19 - 0
src/Avalonia.Animation/Easing/QuarticEaseIn.cs

@@ -0,0 +1,19 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using a quartic equation.
+    /// </summary>
+    public class QuarticEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p2 = progress * progress;
+            return p2 * p2;
+        }
+    }
+}

+ 30 - 0
src/Avalonia.Animation/Easing/QuarticEaseInOut.cs

@@ -0,0 +1,30 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piece-wise quartic equation.
+    /// </summary>
+    public class QuarticEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+
+            if (p < 0.5d)
+            {
+                double p2 = p * p;
+                return 8d * p2 * p2;
+            }
+            else
+            {
+                double f = p - 1d;
+                double f2 = f * f;
+                return -8d * f2 * f2 + 1d;
+            }
+        }
+    }
+}

+ 20 - 0
src/Avalonia.Animation/Easing/QuarticEaseOut.cs

@@ -0,0 +1,20 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using a quartic equation.
+    /// </summary>
+    public class QuarticEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double f = progress - 1d;
+            double f2 = f * f;
+            return -f2 * f2 + 1d;
+        }
+    }
+}

+ 19 - 0
src/Avalonia.Animation/Easing/QuinticEaseIn.cs

@@ -0,0 +1,19 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using a quartic equation.
+    /// </summary>
+    public class QuinticEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p2 = progress * progress;
+            return p2 * p2 * progress;
+        }
+    }
+}

+ 29 - 0
src/Avalonia.Animation/Easing/QuinticEaseInOut.cs

@@ -0,0 +1,29 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a piece-wise quartic equation.
+    /// </summary>
+    public class QuinticEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double p = progress;
+            if (p < 0.5d)
+            {
+                double p2 = p * p;
+                return 16d * p2 * p2 * p;
+            }
+            else
+            {
+                double f = 2d * p - 2d;
+                double f2 = f * f;
+                return 0.5d * f2 * f2 * f + 1d;
+            }
+        }
+    }
+}

+ 20 - 0
src/Avalonia.Animation/Easing/QuinticEaseOut.cs

@@ -0,0 +1,20 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using a quartic equation.
+    /// </summary>
+    public class QuinticEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            double f = (progress - 1d);
+            double f2 = f * f;
+            return f2 * f2 * f + 1d;
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Animation/Easing/SineEaseIn.cs

@@ -0,0 +1,21 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Animation.Utils;
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases in a <see cref="double"/> value 
+    /// using the quarter-wave of sine function.
+    /// </summary>
+    public class SineEaseIn : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return Math.Sin((progress - 1) * EasingUtils.HALFPI) + 1;
+        }
+    }
+}

+ 20 - 0
src/Avalonia.Animation/Easing/SineEaseInOut.cs

@@ -0,0 +1,20 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value 
+    /// using a half sine wave function.
+    /// </summary>
+    public class SineEaseInOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return 0.5d * (1d - Math.Cos(progress * Math.PI));
+        }
+    }
+}

+ 22 - 0
src/Avalonia.Animation/Easing/SineEaseOut.cs

@@ -0,0 +1,22 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Animation.Utils;
+using System;
+
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases out a <see cref="double"/> value 
+    /// using the quarter-wave of sine function
+    /// with shifted phase.
+    /// </summary>
+    public class SineEaseOut : Easing
+    {
+        /// <inheritdoc/>
+        public override double Ease(double progress)
+        {
+            return Math.Sin(progress * EasingUtils.HALFPI);
+        }
+    }
+}

+ 14 - 0
src/Avalonia.Animation/FillMode.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    public enum FillMode
+    {
+        None,
+        Forward,
+        Backward,
+        Both
+    }
+}

+ 23 - 0
src/Avalonia.Animation/FloatTransition.cs

@@ -0,0 +1,23 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Metadata;
+using System;
+using System.Reactive.Linq;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="float"/> types.
+    /// </summary>  
+    public class FloatTransition : Transition<float>
+    {
+        /// <inheritdocs/>
+        public override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue)
+        {
+            var delta = newValue - oldValue;
+            return progress
+                .Select(p => (float)Easing.Ease(p) * delta + oldValue);
+        }
+    }
+}

+ 17 - 0
src/Avalonia.Animation/IAnimation.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Interface for Animation objects
+    /// </summary>
+    public interface IAnimation
+    {
+        /// <summary>
+        /// Apply the animation to the specified control
+        /// </summary>
+        IDisposable Apply(Animatable control, IObservable<bool> match);
+    }
+}

+ 8 - 0
src/Avalonia.Animation/IAnimationSetter.cs

@@ -0,0 +1,8 @@
+namespace Avalonia.Animation
+{
+    public interface IAnimationSetter
+    {
+        AvaloniaProperty Property { get; set; }
+        object Value { get; set; }
+    }
+}

+ 22 - 0
src/Avalonia.Animation/IAnimator.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Interface for Animator objects
+    /// </summary>
+    public interface IAnimator : IList<AnimatorKeyFrame>
+    {
+        /// <summary>
+        /// The target property.
+        /// </summary>
+        AvaloniaProperty Property {get; set;}
+
+        /// <summary>
+        /// Applies the current KeyFrame group to the specified control.
+        /// </summary>
+        IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch);
+    }
+}

+ 3 - 10
src/Avalonia.Animation/IEasing.cs

@@ -1,23 +1,16 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
 {
     /// <summary>
     /// <summary>
-    /// Defines the interface for easing functions.
+    /// Defines the interface for easing classes.
     /// </summary>
     /// </summary>
     public interface IEasing
     public interface IEasing
     {
     {
         /// <summary>
         /// <summary>
         /// Returns the value of the transition for the specified progress.
         /// Returns the value of the transition for the specified progress.
         /// </summary>
         /// </summary>
-        /// <param name="progress">The progress of the transition, from 0 to 1.</param>
-        /// <param name="start">The start value of the transition.</param>
-        /// <param name="finish">The end value of the transition.</param>
-        /// <returns>
-        /// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
-        /// by <paramref name="progress"/>.
-        /// </returns>
-        object Ease(double progress, object start, object finish);
+        double Ease(double progress);
     }
     }
 }
 }

+ 0 - 24
src/Avalonia.Animation/IEasing`1.cs

@@ -1,24 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-namespace Avalonia.Animation
-{
-    /// <summary>
-    /// Defines the interface for easing functions.
-    /// </summary>
-    /// <typeparam name="T">The type of the property being transitioned.</typeparam>
-    public interface IEasing<T> : IEasing
-    {
-        /// <summary>
-        /// Returns the value of the transition for the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress of the transition, from 0 to 1.</param>
-        /// <param name="start">The start value of the transition.</param>
-        /// <param name="finish">The end value of the transition.</param>
-        /// <returns>
-        /// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
-        /// by <paramref name="progress"/>.
-        /// </returns>
-        T Ease(double progress, T start, T finish);
-    }
-}

+ 26 - 0
src/Avalonia.Animation/ITransition.cs

@@ -0,0 +1,26 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Metadata;
+using System;
+using System.Reactive.Linq;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Interface for Transition objects.
+    /// </summary>
+    public interface ITransition
+    {
+        /// <summary>
+        /// Applies the transition to the specified <see cref="Animatable"/>.
+        /// </summary>
+        IDisposable Apply(Animatable control, object oldValue, object newValue);
+
+        /// <summary>
+        /// Gets the property to be animated.
+        /// </summary>
+        AvaloniaProperty Property { get; set; }
+    
+    }
+}

+ 23 - 0
src/Avalonia.Animation/IntegerTransition.cs

@@ -0,0 +1,23 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Metadata;
+using System;
+using System.Reactive.Linq;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="int"/> types.
+    /// </summary>  
+    public class IntegerTransition : Transition<int>
+    {
+        /// <inheritdocs/>
+        public override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue)
+        {
+            var delta = newValue - oldValue;
+            return progress
+                .Select(p => (int)(Easing.Ease(p) * delta + oldValue));
+        }
+    }
+}

+ 78 - 0
src/Avalonia.Animation/KeyFrame.cs

@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.ComponentModel;
+using Avalonia.Metadata;
+using Avalonia.Collections;
+
+namespace Avalonia.Animation
+{
+
+    /// <summary>
+    /// Stores data regarding a specific key
+    /// point and value in an animation.
+    /// </summary>
+    public class KeyFrame : AvaloniaList<IAnimationSetter>
+    {
+        internal bool timeSpanSet, cueSet;
+        private TimeSpan _ktimeSpan;
+        private Cue _kCue;
+
+        public KeyFrame()
+        {
+        }
+
+        public KeyFrame(IEnumerable<IAnimationSetter> items) : base(items)
+        {
+        }
+
+        public KeyFrame(params IAnimationSetter[] items) : base(items)
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the key time of this <see cref="KeyFrame"/>.
+        /// </summary>
+        /// <value>The key time.</value>
+        public TimeSpan KeyTime
+        {
+            get
+            {
+                return _ktimeSpan;
+            }
+            set
+            {
+                if (cueSet)
+                {
+                    throw new InvalidOperationException($"You can only set either {nameof(KeyTime)} or {nameof(Cue)}.");
+                }
+                timeSpanSet = true;
+                _ktimeSpan = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the cue of this <see cref="KeyFrame"/>.
+        /// </summary>
+        /// <value>The cue.</value>
+        public Cue Cue
+        {
+            get
+            {
+                return _kCue;
+            }
+            set
+            {
+                if (timeSpanSet)
+                {
+                    throw new InvalidOperationException($"You can only set either {nameof(KeyTime)} or {nameof(Cue)}.");
+                }
+                cueSet = true;
+                _kCue = value;
+            }
+        }
+
+
+    }
+
+}

+ 41 - 0
src/Avalonia.Animation/KeyFramePair`1.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Collections;
+using System.ComponentModel;
+using Avalonia.Animation.Utils;
+using System.Reactive.Linq;
+using System.Linq;
+using Avalonia.Data;
+using System.Reactive.Disposables;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Represents a pair of keyframe, usually the
+    /// Start and End keyframes of a <see cref="Animator{T}"/> object.
+    /// </summary>
+    public struct KeyFramePair<T>
+    {
+        /// <summary>
+        /// Initializes this <see cref="KeyFramePair{T}"/>
+        /// </summary>
+        /// <param name="FirstKeyFrame"></param>
+        /// <param name="LastKeyFrame"></param>
+        public KeyFramePair(KeyValuePair<double, (T, bool)> FirstKeyFrame, KeyValuePair<double, (T, bool)> LastKeyFrame) : this()
+        {
+            this.FirstKeyFrame = FirstKeyFrame;
+            this.SecondKeyFrame = LastKeyFrame;
+        }
+
+        /// <summary>
+        /// First <see cref="KeyFrame"/> object.
+        /// </summary>
+        public KeyValuePair<double, (T TargetValue, bool isNeutral)> FirstKeyFrame { get; private set; }
+
+        /// <summary>
+        /// Second <see cref="KeyFrame"/> object.
+        /// </summary>
+        public KeyValuePair<double, (T TargetValue, bool isNeutral)> SecondKeyFrame { get; private set; }
+    }
+}

+ 0 - 41
src/Avalonia.Animation/LinearDoubleEasing.cs

@@ -1,41 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-namespace Avalonia.Animation
-{
-    /// <summary>
-    /// Linearly eases a double value.
-    /// </summary>
-    public class LinearDoubleEasing : IEasing<double>
-    {
-        /// <summary>
-        /// Returns the value of the transition for the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress of the transition, from 0 to 1.</param>
-        /// <param name="start">The start value of the transition.</param>
-        /// <param name="finish">The end value of the transition.</param>
-        /// <returns>
-        /// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
-        /// by <paramref name="progress"/>.
-        /// </returns>
-        public double Ease(double progress, double start, double finish)
-        {
-            return ((finish - start) * progress) + start;
-        }
-
-        /// <summary>
-        /// Returns the value of the transition for the specified progress.
-        /// </summary>
-        /// <param name="progress">The progress of the transition, from 0 to 1.</param>
-        /// <param name="start">The start value of the transition.</param>
-        /// <param name="finish">The end value of the transition.</param>
-        /// <returns>
-        /// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
-        /// by <paramref name="progress"/>.
-        /// </returns>
-        object IEasing.Ease(double progress, object start, object finish)
-        {
-            return Ease(progress, (double)start, (double)finish);
-        }
-    }
-}

+ 0 - 35
src/Avalonia.Animation/LinearEasing.cs

@@ -1,35 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-
-namespace Avalonia.Animation
-{
-    /// <summary>
-    /// Returns a linear <see cref="IEasing"/> for the specified type.
-    /// </summary>
-    /// <remarks>
-    /// Unfortunately this class is needed as there's no way to create a true generic easing
-    /// function at compile time, as mathematical operators don't have an interface.
-    /// </remarks>
-    public static class LinearEasing
-    {
-        /// <summary>
-        /// A linear easing function for the specified type.
-        /// </summary>
-        /// <typeparam name="T">The type.</typeparam>
-        /// <returns>An easing function.</returns>
-        public static IEasing<T> For<T>()
-        {
-            if (typeof(T) == typeof(double))
-            {
-                return (IEasing<T>)new LinearDoubleEasing();
-            }
-            else
-            {
-                throw new NotSupportedException(
-                    $"Don't know how to create a LinearEasing for type '{typeof(T).FullName}'.");
-            }
-        }
-    }
-}

+ 27 - 0
src/Avalonia.Animation/PlayState.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Determines the playback state of an animation.
+    /// </summary>
+    public enum PlayState
+    {
+        /// <summary>
+        /// The animation is running.
+        /// </summary>
+        Run,
+
+        /// <summary>
+        /// The animation is paused.
+        /// </summary>
+        Pause,
+        
+        /// <summary>
+        /// The animation is stopped.
+        /// </summary>
+        Stop
+    }
+}

+ 32 - 0
src/Avalonia.Animation/PlaybackDirection.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Determines the playback direction of an animation.
+    /// </summary>
+    public enum PlaybackDirection
+    {
+        /// <summary>
+        /// The animation is played normally.
+        /// </summary>
+        Normal,
+
+        /// <summary>
+        /// The animation is played in reverse direction.
+        /// </summary>
+        Reverse,
+
+        /// <summary>
+        /// The animation is played forwards first, then backwards.
+        /// </summary>
+        Alternate,
+
+        /// <summary>
+        /// The animation is played backwards first, then forwards.
+        /// </summary>
+        AlternateReverse
+    }
+}

+ 8 - 0
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@@ -0,0 +1,8 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Metadata;
+using System.Reflection;
+
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]

+ 0 - 50
src/Avalonia.Animation/PropertyTransition.cs

@@ -1,50 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-
-namespace Avalonia.Animation
-{
-    /// <summary>
-    /// Defines how a property should be animated using a transition.
-    /// </summary>
-    public class PropertyTransition
-    {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="PropertyTransition"/> class.
-        /// </summary>
-        /// <param name="property">The property to be animated/</param>
-        /// <param name="duration">The duration of the animation.</param>
-        /// <param name="easing">The easing function to use.</param>
-        public PropertyTransition(AvaloniaProperty property, TimeSpan duration, IEasing easing)
-        {
-            Property = property;
-            Duration = duration;
-            Easing = easing;
-        }
-
-        /// <summary>
-        /// Gets the property to be animated.
-        /// </summary>
-        /// <value>
-        /// The property to be animated.
-        /// </value>
-        public AvaloniaProperty Property { get; }
-
-        /// <summary>
-        /// Gets the duration of the animation.
-        /// </summary>
-        /// <value>
-        /// The duration of the animation.
-        /// </value>
-        public TimeSpan Duration { get; }
-
-        /// <summary>
-        /// Gets the easing function used.
-        /// </summary>
-        /// <value>
-        /// The easing function.
-        /// </value>
-        public IEasing Easing { get; }
-    }
-}

+ 202 - 0
src/Avalonia.Animation/RepeatCount.cs

@@ -0,0 +1,202 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Utilities;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Defines the valid modes for a <see cref="RepeatCount"/>.
+    /// </summary>
+    public enum RepeatType
+    {
+        None,
+        Repeat,
+        Loop
+    }
+
+    /// <summary>
+    /// Determines the number of iterations of an animation.
+    /// Also defines its repeat behavior. 
+    /// </summary>
+    [TypeConverter(typeof(RepeatCountTypeConverter))]
+    public struct RepeatCount : IEquatable<RepeatCount>
+    {
+        private readonly RepeatType _type;
+        private readonly ulong _value;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RepeatCount"/> struct.
+        /// </summary>
+        /// <param name="value">The number of iterations of an animation.</param>
+        public RepeatCount(ulong value)
+            : this(value, RepeatType.Repeat)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RepeatCount"/> struct.
+        /// </summary>
+        /// <param name="value">The size of the RepeatCount.</param>
+        /// <param name="type">The unit of the RepeatCount.</param>
+        public RepeatCount(ulong value, RepeatType type)
+        {
+            if (type < RepeatType.None || type > RepeatType.Loop)
+            {
+                throw new ArgumentException("Invalid value", "type");
+            }
+
+            _type = type;
+            _value = value;
+        }
+
+        /// <summary>
+        /// Gets an instance of <see cref="RepeatCount"/> that indicates that an animation
+        /// should repeat forever.
+        /// </summary>
+        public static RepeatCount Loop => new RepeatCount(0, RepeatType.Loop);
+
+        /// <summary>
+        /// Gets an instance of <see cref="RepeatCount"/> that indicates that an animation
+        /// should not repeat.
+        /// </summary>
+        public static RepeatCount None => new RepeatCount(0, RepeatType.None);
+
+        /// <summary>
+        /// Gets the unit of the <see cref="RepeatCount"/>.
+        /// </summary>
+        public RepeatType RepeatType => _type;
+
+        /// <summary>
+        /// Gets a value that indicates whether the <see cref="RepeatCount"/> is set to loop.
+        /// </summary>
+        public bool IsLoop => _type == RepeatType.Loop;
+
+        /// <summary>
+        /// Gets a value that indicates whether the <see cref="RepeatCount"/> is set to not repeat.
+        /// </summary>
+        public bool IsNone => _type == RepeatType.None;
+
+        /// <summary>
+        /// Gets the number of repeat iterations.
+        /// </summary>
+        public ulong Value => _value;
+
+        /// <summary>
+        /// Compares two RepeatCount structures for equality.
+        /// </summary>
+        /// <param name="a">The first RepeatCount.</param>
+        /// <param name="b">The second RepeatCount.</param>
+        /// <returns>True if the structures are equal, otherwise false.</returns>
+        public static bool operator ==(RepeatCount a, RepeatCount b)
+        {
+            return (a.IsNone && b.IsNone) && (a.IsLoop && b.IsLoop)
+                || (a._value == b._value && a._type == b._type);
+        }
+
+        /// <summary>
+        /// Compares two RepeatCount structures for inequality.
+        /// </summary>
+        /// <param name="rc1">The first RepeatCount.</param>
+        /// <param name="rc2">The first RepeatCount.</param>
+        /// <returns>True if the structures are unequal, otherwise false.</returns>
+        public static bool operator !=(RepeatCount rc1, RepeatCount rc2)
+        {
+            return !(rc1 == rc2);
+        }
+
+        /// <summary>
+        /// Determines whether the <see cref="RepeatCount"/> is equal to the specified object.
+        /// </summary>
+        /// <param name="o">The object with which to test equality.</param>
+        /// <returns>True if the objects are equal, otherwise false.</returns>
+        public override bool Equals(object o)
+        {
+            if (o == null)
+            {
+                return false;
+            }
+
+            if (!(o is RepeatCount))
+            {
+                return false;
+            }
+
+            return this == (RepeatCount)o;
+        }
+
+        /// <summary>
+        /// Compares two RepeatCount structures for equality.
+        /// </summary>
+        /// <param name="RepeatCount">The structure with which to test equality.</param>
+        /// <returns>True if the structures are equal, otherwise false.</returns>
+        public bool Equals(RepeatCount RepeatCount)
+        {
+            return this == RepeatCount;
+        }
+
+        /// <summary>
+        /// Gets a hash code for the RepeatCount.
+        /// </summary>
+        /// <returns>The hash code.</returns>
+        public override int GetHashCode()
+        {
+            return _value.GetHashCode() ^ _type.GetHashCode();
+        }
+
+        /// <summary>
+        /// Gets a string representation of the <see cref="RepeatCount"/>.
+        /// </summary>
+        /// <returns>The string representation.</returns>
+        public override string ToString()
+        {
+            if (IsLoop)
+            {
+                return "Auto";
+            }
+            else if (IsNone)
+            {
+                return "None";
+            }
+
+            string s = _value.ToString();
+            return s;
+        }
+
+        /// <summary>
+        /// Parses a string to return a <see cref="RepeatCount"/>.
+        /// </summary>
+        /// <param name="s">The string.</param>
+        /// <returns>The <see cref="RepeatCount"/>.</returns>
+        public static RepeatCount Parse(string s)
+        {
+            s = s.ToUpperInvariant().Trim();
+
+            if (s == "NONE")
+            {
+                return None;
+            }
+            else if (s.EndsWith("LOOP"))
+            {
+                return Loop;
+            }
+            else
+            {
+                if(s.StartsWith("-"))
+                    throw new InvalidCastException("RepeatCount can't be a negative number.");
+
+                var value = ulong.Parse(s, CultureInfo.InvariantCulture);
+             
+                if (value == 1)
+                    return None;
+
+                return new RepeatCount(value);
+            }
+        }
+    }
+}

+ 23 - 0
src/Avalonia.Animation/RepeatCountTypeConverter.cs

@@ -0,0 +1,23 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Animation.Easings;
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Avalonia.Animation
+{
+    public class RepeatCountTypeConverter : TypeConverter
+    {
+        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        {
+            return RepeatCount.Parse((string)value);
+        }
+    }
+}

+ 123 - 0
src/Avalonia.Animation/Timing.cs

@@ -0,0 +1,123 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Reactive.Linq;
+using Avalonia.Data;
+using Avalonia.Threading;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Provides global timing functions for animations.
+    /// </summary>
+    public static class Timing
+    {
+        static ulong _transitionsFrameCount;
+        static PlayState _globalState = PlayState.Run;
+
+        /// <summary>
+        /// The number of frames per second.
+        /// </summary>
+        public const int FramesPerSecond = 60;
+
+        /// <summary>
+        /// The time span of each frame.
+        /// </summary>
+        internal static readonly TimeSpan FrameTick = TimeSpan.FromSeconds(1.0 / FramesPerSecond);
+
+        /// <summary>
+        /// Initializes static members of the <see cref="Timing"/> class.
+        /// </summary>
+        static Timing()
+        {
+            var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance);
+
+            AnimationStateTimer = globalTimer
+                .Select(_ =>
+                {
+                    return _globalState;
+                })
+                .Publish()
+                .RefCount();
+
+            TransitionsTimer = globalTimer
+                               .Select(p => _transitionsFrameCount++)
+                               .Publish()
+                               .RefCount();
+        }
+
+
+        /// <summary>
+        /// Sets the animation play state for all animations
+        /// </summary>
+        public static void SetGlobalPlayState(PlayState playState)
+        {
+            Dispatcher.UIThread.VerifyAccess();
+            _globalState = playState;
+        }
+
+        /// <summary>
+        /// Gets the animation play state for all animations
+        /// </summary>
+        public static PlayState GetGlobalPlayState()
+        {
+            Dispatcher.UIThread.VerifyAccess();
+            return _globalState;
+        }
+
+        /// <summary>
+        /// Gets the animation timer.
+        /// </summary>
+        /// <remarks>
+        /// The animation timer triggers usually at 60 times per second or as
+        /// defined in <see cref="FramesPerSecond"/>.
+        /// The parameter passed to a subsciber is the current playstate of the animation.
+        /// </remarks>
+        internal static IObservable<PlayState> AnimationStateTimer
+        {
+            get;
+        }
+
+        /// <summary>
+        /// Gets the transitions timer.
+        /// </summary>
+        /// <remarks>
+        /// The transitions timer increments usually 60 times per second as
+        /// defined in <see cref="FramesPerSecond"/>.
+        /// The parameter passed to a subsciber is the number of frames since the animation system was
+        /// initialized.
+        /// </remarks>
+        public static IObservable<ulong> TransitionsTimer
+        {
+            get;
+        }
+
+        /// <summary>
+        /// Gets a timer that fires every frame for the specified duration with delay.
+        /// </summary>
+        /// <returns>
+        /// An observable that notifies the subscriber of the progress along the transition.
+        /// </returns>
+        /// <remarks>
+        /// The parameter passed to the subscriber is the progress along the transition, with
+        /// 0 being the start and 1 being the end. The observable is guaranteed to fire 0
+        /// immediately on subscribe and 1 at the end of the duration.
+        /// </remarks>
+        public static IObservable<double> GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan))
+        {
+            var startTime = _transitionsFrameCount;
+            var _duration = (ulong)(duration.Ticks / FrameTick.Ticks);
+            var endTime = startTime + _duration;
+
+            return TransitionsTimer
+                .TakeWhile(x => x < endTime)
+                .Select(x => (double)(x - startTime) / _duration)
+                .StartWith(0.0)
+                .Concat(Observable.Return(1.0));
+        }
+
+    }
+}

+ 68 - 0
src/Avalonia.Animation/Transition`1.cs

@@ -0,0 +1,68 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Metadata;
+using System;
+using System.Reactive.Linq;
+using Avalonia.Animation.Easings;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Defines how a property should be animated using a transition.
+    /// </summary>
+    public abstract class Transition<T> : AvaloniaObject, ITransition
+    {
+        private AvaloniaProperty _prop;
+        private Easing _easing;
+
+        /// <summary>
+        /// Gets the duration of the animation.
+        /// </summary> 
+        public TimeSpan Duration { get; set; }
+
+        /// <summary>
+        /// Gets the easing class to be used.
+        /// </summary>
+        public Easing Easing
+        {
+            get
+            {
+                return _easing ?? (_easing = new LinearEasing());
+            }
+            set
+            {
+                _easing = value;
+            }
+        }
+
+        /// <inheritdocs/>
+        public AvaloniaProperty Property
+        {
+            get
+            {
+                return _prop;
+            }
+            set
+            {
+                if (!(value.PropertyType.IsAssignableFrom(typeof(T))))
+                    throw new InvalidCastException
+                        ($"Invalid property type \"{typeof(T).Name}\" for this transition: {GetType().Name}.");
+
+                _prop = value;
+            }
+        }
+
+        /// <summary>
+        /// Apply interpolation to the property.
+        /// </summary>
+        public abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue);
+
+        /// <inheritdocs/>
+        public virtual IDisposable Apply(Animatable control, object oldValue, object newValue)
+        {
+            var transition = DoTransition(Timing.GetTransitionsTimer(control, Duration, TimeSpan.Zero), (T)oldValue, (T)newValue).Select(p => (object)p);
+            return control.Bind(Property, transition, Data.BindingPriority.Animation);
+        }
+    }
+}

+ 4 - 4
src/Avalonia.Animation/PropertyTransitions.cs → src/Avalonia.Animation/Transitions.cs

@@ -6,14 +6,14 @@ using Avalonia.Collections;
 namespace Avalonia.Animation
 namespace Avalonia.Animation
 {
 {
     /// <summary>
     /// <summary>
-    /// A collection of <see cref="PropertyTransition"/> definitions.
+    /// A collection of <see cref="ITransition"/> definitions.
     /// </summary>
     /// </summary>
-    public class PropertyTransitions : AvaloniaList<PropertyTransition>
+    public class Transitions : AvaloniaList<ITransition>
     {
     {
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="PropertyTransitions"/> class.
+        /// Initializes a new instance of the <see cref="Transitions"/> class.
         /// </summary>
         /// </summary>
-        public PropertyTransitions()
+        public Transitions()
         {
         {
             ResetBehavior = ResetBehavior.Remove;
             ResetBehavior = ResetBehavior.Remove;
         }
         }

+ 38 - 0
src/Avalonia.Animation/Utils/BounceEaseUtils.cs

@@ -0,0 +1,38 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Animation.Utils
+{
+    /// <summary>
+    /// Helper static class for BounceEase classes.
+    /// </summary>
+    internal static class BounceEaseUtils
+    {
+        /// <summary>
+        /// Returns the consequent <see cref="double"/> value of
+        /// a simulated bounce function.
+        /// </summary>
+        /// <param name="progress">The amount of progress from 0 to 1.</param>
+        /// <returns>The result of the easing function</returns>
+        internal static double Bounce(double progress)
+        {
+            double p = progress;
+            if (p < 4d / 11.0d)
+            {
+                return (121d * p * p) / 16.0d;
+            }
+            else if (p < 8d / 11.0d)
+            {
+                return (363d / 40.0d * p * p) - (99d / 10.0d * p) + 17d / 5.0d;
+            }
+            else if (p < 9d / 10.0d)
+            {
+                return (4356d / 361.0d * p * p) - (35442d / 1805.0d * p) + 16061d / 1805.0d;
+            }
+            else
+            {
+                return (54d / 5.0d * p * p) - (513d / 25.0d * p) + 268d / 25.0d;
+            }
+        } 
+    }
+}

+ 18 - 0
src/Avalonia.Animation/Utils/DoubleUtils.cs

@@ -0,0 +1,18 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation.Utils
+{
+    internal static class DoubleUtils
+    {
+        internal static bool AboutEqual(double x, double y)
+        {
+            double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
+            return Math.Abs(x - y) <= epsilon;
+        }
+    }
+}

+ 18 - 0
src/Avalonia.Animation/Utils/EasingUtils.cs

@@ -0,0 +1,18 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+
+namespace Avalonia.Animation.Utils
+{
+    /// <summary>
+    /// Helper static class for easing mathematical constants.
+    /// </summary>
+    internal static class EasingUtils
+    {
+        /// <summary>
+        /// Half of <see cref="Math.PI"/>
+        /// </summary>
+        internal static double HALFPI = Math.PI / 2d;
+    }
+}

+ 5 - 0
src/Avalonia.Base/Logging/LogArea.cs

@@ -18,6 +18,11 @@ namespace Avalonia.Logging
         /// </summary>
         /// </summary>
         public const string Binding = "Binding";
         public const string Binding = "Binding";
 
 
+        /// <summary>
+        /// The log event comes from the animations system.
+        /// </summary>
+        public const string Animations = "Animations";
+
         /// <summary>
         /// <summary>
         /// The log event comes from the visual system.
         /// The log event comes from the visual system.
         /// </summary>
         /// </summary>

+ 6 - 6
src/Avalonia.Controls/Carousel.cs

@@ -21,10 +21,10 @@ namespace Avalonia.Controls
             AvaloniaProperty.Register<Carousel, bool>(nameof(IsVirtualized), true);
             AvaloniaProperty.Register<Carousel, bool>(nameof(IsVirtualized), true);
 
 
         /// <summary>
         /// <summary>
-        /// Defines the <see cref="Transition"/> property.
+        /// Defines the <see cref="PageTransition"/> property.
         /// </summary>
         /// </summary>
-        public static readonly StyledProperty<IPageTransition> TransitionProperty =
-            AvaloniaProperty.Register<Carousel, IPageTransition>(nameof(Transition));
+        public static readonly StyledProperty<IPageTransition> PageTransitionProperty =
+            AvaloniaProperty.Register<Carousel, IPageTransition>(nameof(PageTransition));
 
 
         /// <summary>
         /// <summary>
         /// The default value of <see cref="ItemsControl.ItemsPanelProperty"/> for 
         /// The default value of <see cref="ItemsControl.ItemsPanelProperty"/> for 
@@ -57,10 +57,10 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Gets or sets the transition to use when moving between pages.
         /// Gets or sets the transition to use when moving between pages.
         /// </summary>
         /// </summary>
-        public IPageTransition Transition
+        public IPageTransition PageTransition
         {
         {
-            get { return GetValue(TransitionProperty); }
-            set { SetValue(TransitionProperty, value); }
+            get { return GetValue(PageTransitionProperty); }
+            set { SetValue(PageTransitionProperty, value); }
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 10 - 9
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@@ -33,12 +33,13 @@ namespace Avalonia.Controls.Presenters
                 (o, v) => o.SelectedIndex = v);
                 (o, v) => o.SelectedIndex = v);
 
 
         /// <summary>
         /// <summary>
-        /// Defines the <see cref="Transition"/> property.
+        /// Defines the <see cref="PageTransition"/> property.
         /// </summary>
         /// </summary>
-        public static readonly StyledProperty<IPageTransition> TransitionProperty =
-            Carousel.TransitionProperty.AddOwner<CarouselPresenter>();
+        public static readonly StyledProperty<IPageTransition> PageTransitionProperty =
+            Carousel.PageTransitionProperty.AddOwner<CarouselPresenter>();
 
 
         private int _selectedIndex = -1;
         private int _selectedIndex = -1;
+       // private Task _current;
         private Task _currentTransition;
         private Task _currentTransition;
         private int _queuedTransitionIndex = -1;
         private int _queuedTransitionIndex = -1;
 
 
@@ -88,10 +89,10 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// <summary>
         /// Gets or sets a transition to use when switching pages.
         /// Gets or sets a transition to use when switching pages.
         /// </summary>
         /// </summary>
-        public IPageTransition Transition
+        public IPageTransition PageTransition
         {
         {
-            get { return GetValue(TransitionProperty); }
-            set { SetValue(TransitionProperty, value); }
+            get { return GetValue(PageTransitionProperty); }
+            set { SetValue(PageTransitionProperty, value); }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -121,7 +122,7 @@ namespace Avalonia.Controls.Presenters
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Moves to the selected page, animating if a <see cref="Transition"/> is set.
+        /// Moves to the selected page, animating if a <see cref="PageTransition"/> is set.
         /// </summary>
         /// </summary>
         /// <param name="fromIndex">The index of the old page.</param>
         /// <param name="fromIndex">The index of the old page.</param>
         /// <param name="toIndex">The index of the new page.</param>
         /// <param name="toIndex">The index of the new page.</param>
@@ -144,9 +145,9 @@ namespace Avalonia.Controls.Presenters
                     to = GetOrCreateContainer(toIndex);
                     to = GetOrCreateContainer(toIndex);
                 }
                 }
 
 
-                if (Transition != null && (from != null || to != null))
+                if (PageTransition != null && (from != null || to != null))
                 {
                 {
-                    await Transition.Start((Visual)from, (Visual)to, fromIndex < toIndex);
+                    await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex);
                 }
                 }
                 else if (to != null)
                 else if (to != null)
                 {
                 {

+ 6 - 29
src/Avalonia.Controls/ProgressBar.cs

@@ -102,11 +102,13 @@ namespace Avalonia.Controls
             UpdateIndicator(Bounds.Size);
             UpdateIndicator(Bounds.Size);
         }
         }
 
 
+        // TODO: Implement Indeterminate Progress animation
+        //       in xaml (most ideal) or if it's not possible
+        //       then on this class.
         private class IndeterminateAnimation : IDisposable
         private class IndeterminateAnimation : IDisposable
         {
         {
             private WeakReference<ProgressBar> _progressBar;
             private WeakReference<ProgressBar> _progressBar;
-            private IDisposable _indeterminateBindSubscription;
-            private TimeSpan _startTime;
+
             private bool _disposed;
             private bool _disposed;
 
 
             public bool Disposed => _disposed;
             public bool Disposed => _disposed;
@@ -114,12 +116,7 @@ namespace Avalonia.Controls
             private IndeterminateAnimation(ProgressBar progressBar)
             private IndeterminateAnimation(ProgressBar progressBar)
             {
             {
                 _progressBar = new WeakReference<ProgressBar>(progressBar);
                 _progressBar = new WeakReference<ProgressBar>(progressBar);
-                _startTime = Animate.Stopwatch.Elapsed;
-                _indeterminateBindSubscription = Animate.Timer.TakeWhile(x => (x - _startTime).TotalSeconds <= 4.0)
-                                                              .Select(GetAnimationRect)
-                                                              .Finally(() => _startTime = Animate.Stopwatch.Elapsed)
-                                                              .Repeat()
-                                                              .Subscribe(AnimationTick);
+
             }
             }
 
 
             public static IndeterminateAnimation StartAnimation(ProgressBar progressBar)
             public static IndeterminateAnimation StartAnimation(ProgressBar progressBar)
@@ -129,31 +126,11 @@ namespace Avalonia.Controls
 
 
             private Rect GetAnimationRect(TimeSpan time)
             private Rect GetAnimationRect(TimeSpan time)
             {
             {
-                if (_progressBar.TryGetTarget(out var progressBar))
-                {
-                    if (progressBar.Orientation == Orientation.Horizontal)
-                        return new Rect(-progressBar._indicator.Width - 5 + (time - _startTime).TotalSeconds / 4.0 * (progressBar.Bounds.Width + progressBar._indicator.Width + 10), 0, progressBar._indicator.Bounds.Width, progressBar._indicator.Bounds.Height);
-                    else
-                        return new Rect(0, progressBar.Bounds.Height + 5 - (time - _startTime).TotalSeconds / 4.0 * (progressBar.Bounds.Height + progressBar._indicator.Height + 10), progressBar._indicator.Bounds.Width, progressBar._indicator.Bounds.Height);
-                }
-                else
-                {
-                    _indeterminateBindSubscription.Dispose();
-                    return Rect.Empty;
-                }
-            }
-
-            private void AnimationTick(Rect rect)
-            {
-                if (_progressBar.TryGetTarget(out var progressBar))
-                    progressBar._indicator.Arrange(rect);
-                else
-                    _indeterminateBindSubscription.Dispose();
+                return Rect.Empty;
             }
             }
 
 
             public void Dispose()
             public void Dispose()
             {
             {
-                _indeterminateBindSubscription?.Dispose();
                 _disposed = true;
                 _disposed = true;
             }
             }
         }
         }

+ 6 - 6
src/Avalonia.Controls/TabControl.cs

@@ -14,10 +14,10 @@ namespace Avalonia.Controls
     public class TabControl : SelectingItemsControl
     public class TabControl : SelectingItemsControl
     {
     {
         /// <summary>
         /// <summary>
-        /// Defines the <see cref="Transition"/> property.
+        /// Defines the <see cref="PageTransition"/> property.
         /// </summary>
         /// </summary>
-        public static readonly StyledProperty<IPageTransition> TransitionProperty =
-            Avalonia.Controls.Carousel.TransitionProperty.AddOwner<TabControl>();
+        public static readonly StyledProperty<IPageTransition> PageTransitionProperty =
+            Avalonia.Controls.Carousel.PageTransitionProperty.AddOwner<TabControl>();
 
 
         /// <summary>
         /// <summary>
         /// Defines an <see cref="IMemberSelector"/> that selects the content of a <see cref="TabItem"/>.
         /// Defines an <see cref="IMemberSelector"/> that selects the content of a <see cref="TabItem"/>.
@@ -68,10 +68,10 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// Gets or sets the transition to use when switching tabs.
         /// Gets or sets the transition to use when switching tabs.
         /// </summary>
         /// </summary>
-        public IPageTransition Transition
+        public IPageTransition PageTransition
         {
         {
-            get { return GetValue(TransitionProperty); }
-            set { SetValue(TransitionProperty, value); }
+            get { return GetValue(PageTransitionProperty); }
+            set { SetValue(PageTransitionProperty, value); }
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 1 - 1
src/Avalonia.Styling/Avalonia.Styling.csproj

@@ -5,9 +5,9 @@
     <RootNamespace>Avalonia</RootNamespace>
     <RootNamespace>Avalonia</RootNamespace>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
   </ItemGroup>
   </ItemGroup>
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\Rx.props" />
 </Project>
 </Project>

+ 25 - 0
src/Avalonia.Styling/Styling/Style.cs

@@ -7,6 +7,8 @@ using System.Collections.Specialized;
 using System.Reactive.Linq;
 using System.Reactive.Linq;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
+using Avalonia.Animation;
+using System.Diagnostics;
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
@@ -20,6 +22,8 @@ namespace Avalonia.Styling
         private IResourceNode _parent;
         private IResourceNode _parent;
         private IResourceDictionary _resources;
         private IResourceDictionary _resources;
 
 
+        private IList<IAnimation> _animations;
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="Style"/> class.
         /// Initializes a new instance of the <see cref="Style"/> class.
         /// </summary>
         /// </summary>
@@ -78,6 +82,14 @@ namespace Avalonia.Styling
         [Content]
         [Content]
         public IList<ISetter> Setters { get; set; } = new List<ISetter>();
         public IList<ISetter> Setters { get; set; } = new List<ISetter>();
 
 
+        public IList<IAnimation> Animations
+        {
+            get
+            {
+                return _animations ?? (_animations = new List<IAnimation>());
+            }
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         IResourceNode IResourceNode.ResourceParent => _parent;
         IResourceNode IResourceNode.ResourceParent => _parent;
 
 
@@ -101,6 +113,19 @@ namespace Avalonia.Styling
                 {
                 {
                     var subs = GetSubscriptions(control);
                     var subs = GetSubscriptions(control);
 
 
+                    foreach (var animation in Animations)
+                    {
+                        IObservable<bool> obsMatch = match.ObservableResult;
+
+                        if (match.ImmediateResult == true)
+                        {
+                            obsMatch = Observable.Return(true);
+                        } 
+
+                        var sub = animation.Apply((Animatable)control, obsMatch);
+                        subs.Add(sub);
+                    }
+
                     foreach (var setter in Setters)
                     foreach (var setter in Setters)
                     {
                     {
                         var sub = setter.Apply(this, control, match.ObservableResult);
                         var sub = setter.Apply(this, control, match.ObservableResult);

+ 2 - 1
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@@ -128,7 +128,8 @@ namespace Avalonia.Styling
                         x => control.Classes.CollectionChanged += x,
                         x => control.Classes.CollectionChanged += x,
                         x => control.Classes.CollectionChanged -= x)
                         x => control.Classes.CollectionChanged -= x)
                         .StartWith((EventPattern<NotifyCollectionChangedEventArgs>)null)
                         .StartWith((EventPattern<NotifyCollectionChangedEventArgs>)null)
-                        .Select(_ => Matches(control.Classes));
+                        .Select(_ => Matches(control.Classes))
+                        .DistinctUntilChanged();
                     return new SelectorMatch(observable);
                     return new SelectorMatch(observable);
                 }
                 }
                 else
                 else

+ 1 - 1
src/Avalonia.Themes.Default/Carousel.xaml

@@ -10,7 +10,7 @@
                            Margin="{TemplateBinding Padding}"
                            Margin="{TemplateBinding Padding}"
                            MemberSelector="{TemplateBinding MemberSelector}"
                            MemberSelector="{TemplateBinding MemberSelector}"
                            SelectedIndex="{TemplateBinding SelectedIndex}"
                            SelectedIndex="{TemplateBinding SelectedIndex}"
-                           Transition="{TemplateBinding Transition}"/>
+                           PageTransition="{TemplateBinding PageTransition}"/>
       </Border>
       </Border>
     </ControlTemplate>
     </ControlTemplate>
   </Setter>
   </Setter>

+ 1 - 1
src/Avalonia.Themes.Default/TabControl.xaml

@@ -14,7 +14,7 @@
                       MemberSelector="{x:Static TabControl.ContentSelector}"
                       MemberSelector="{x:Static TabControl.ContentSelector}"
                       Items="{TemplateBinding Items}"
                       Items="{TemplateBinding Items}"
                       SelectedIndex="{TemplateBinding Path=SelectedIndex}"
                       SelectedIndex="{TemplateBinding Path=SelectedIndex}"
-                      Transition="{TemplateBinding Transition}"
+                      PageTransition="{TemplateBinding PageTransition}"
                       Grid.Row="1"/>
                       Grid.Row="1"/>
           </DockPanel>
           </DockPanel>
         </Border>
         </Border>

+ 4 - 15
src/Avalonia.Visuals/Animation/CrossFade.cs

@@ -51,6 +51,8 @@ namespace Avalonia.Animation
         {
         {
             var tasks = new List<Task>();
             var tasks = new List<Task>();
 
 
+            // TODO: Implement relevant transition logic here (or discard this class)
+            // in favor of XAML based transition for pages
             if (to != null)
             if (to != null)
             {
             {
                 to.Opacity = 0;
                 to.Opacity = 0;
@@ -58,13 +60,6 @@ namespace Avalonia.Animation
 
 
             if (from != null)
             if (from != null)
             {
             {
-                tasks.Add(Animate.Property(
-                    (IAvaloniaObject)from,
-                    Visual.OpacityProperty,
-                    from.Opacity,
-                    0,
-                    LinearEasing.For<double>(),
-                    Duration).ToTask());
             }
             }
 
 
             if (to != null)
             if (to != null)
@@ -72,16 +67,10 @@ namespace Avalonia.Animation
                 to.Opacity = 0;
                 to.Opacity = 0;
                 to.IsVisible = true;
                 to.IsVisible = true;
 
 
-                tasks.Add(Animate.Property(
-                    (IAvaloniaObject)to,
-                    Visual.OpacityProperty,
-                    0,
-                    1,
-                    LinearEasing.For<double>(),
-                    Duration).ToTask());
             }
             }
 
 
-            await Task.WhenAll(tasks.ToArray());
+            // FIXME: This is temporary until animations are fixed.
+            await Task.Delay(1);
 
 
             if (from != null)
             if (from != null)
             {
             {

+ 7 - 20
src/Avalonia.Visuals/Animation/PageSlide.cs

@@ -74,34 +74,21 @@ namespace Avalonia.Animation
             var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
             var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
             var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty;
             var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty;
 
 
+
+            // TODO: Implement relevant transition logic here (or discard this class)
+            // in favor of XAML based transition for pages
             if (from != null)
             if (from != null)
             {
             {
-                var transform = new TranslateTransform();
-                from.RenderTransform = transform;
-                tasks.Add(Animate.Property(
-                    transform,
-                    translateProperty,
-                    0.0,
-                    forward ? -distance : distance,
-                    LinearEasing.For<double>(),
-                    Duration).ToTask());
+
             }
             }
 
 
             if (to != null)
             if (to != null)
             {
             {
-                var transform = new TranslateTransform();
-                to.RenderTransform = transform;
-                to.IsVisible = true;
-                tasks.Add(Animate.Property(
-                    transform,
-                    translateProperty,
-                    forward ? distance : -distance,
-                    0.0,
-                    LinearEasing.For<double>(),
-                    Duration).ToTask());
+
             }
             }
 
 
-            await Task.WhenAll(tasks.ToArray());
+            // FIXME: This is temporary until animations are fixed.
+            await Task.Delay(1);
 
 
             if (from != null)
             if (from != null)
             {
             {

+ 31 - 0
src/Avalonia.Visuals/Animation/PointTransition.cs

@@ -0,0 +1,31 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Metadata;
+using System;
+using System.Reactive.Linq;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Point"/> type.
+    /// </summary>  
+    public class PointTransition : Transition<Point>
+    {
+        /// <inheritdocs/>
+        public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue)
+        {
+            var deltaX = newValue.X - oldValue.Y;
+            var deltaY = newValue.X - oldValue.Y;
+
+            return progress
+                .Select(p =>
+                {
+                    var f = Easing.Ease(p);
+                    var nX = f * deltaX + oldValue.X;
+                    var nY = f * deltaY + oldValue.Y;
+                    return new Point(nX, nY);
+                });
+        }
+    }
+}

+ 35 - 0
src/Avalonia.Visuals/Animation/ThicknessTransition.cs

@@ -0,0 +1,35 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Metadata;
+using System;
+using System.Reactive.Linq;
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Thickness"/> type.
+    /// </summary>  
+    public class ThicknessTransition : Transition<Thickness>
+    {
+        /// <inheritdocs/>
+        public override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue)
+        {
+            var deltaL = newValue.Left - oldValue.Left;
+            var deltaT = newValue.Top - oldValue.Top;
+            var deltaR = newValue.Right - oldValue.Right;
+            var deltaB = newValue.Bottom - oldValue.Bottom;
+
+            return progress
+                .Select(p => 
+                {
+                    var f = Easing.Ease(p);
+                    var nL = f * deltaL + oldValue.Left;
+                    var nT = f * deltaT + oldValue.Right;
+                    var nR = f * deltaR + oldValue.Top;
+                    var nB = f * deltaB + oldValue.Bottom;
+                    return new Thickness(nL, nT, nR, nB);
+                });
+        }
+    }
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików