Browse Source

*Implement Basic Keyframe Animations support.
*Implement DoubleKeyFrames for Properties such as Opacity, etc.
*Implement TransformKeyFrames with initial implementation
of specialized logic for selecting RenderTransform of the
target control properly.
*Ported RenderTest to .NET Core for testing and to remove the
crufty old .csproj format.
*Replaced AnimationsPage with some samples of hover-activated
animated red rectangles.

Jumar Macato 7 years ago
parent
commit
dd657e203d
65 changed files with 756 additions and 247 deletions
  1. 2 1
      samples/RenderTest/MainWindow.xaml
  2. 70 0
      samples/RenderTest/Pages/AnimationsPage.xaml
  3. 1 1
      samples/RenderTest/Pages/AnimationsPage.xaml.cs
  4. 24 151
      samples/RenderTest/RenderTest.csproj
  5. 1 1
      samples/RenderTest/SideBar.xaml
  6. 1 1
      samples/RenderTest/ViewModels/MainWindowViewModel.cs
  7. 4 3
      src/Avalonia.Animation/Animatable.cs
  8. 41 24
      src/Avalonia.Animation/Animation.cs
  9. 1 1
      src/Avalonia.Animation/Easing.cs
  10. 1 1
      src/Avalonia.Animation/Easing/BackEaseIn.cs
  11. 1 1
      src/Avalonia.Animation/Easing/BackEaseOut.cs
  12. 4 2
      src/Avalonia.Animation/Easing/BounceEaseIn.cs
  13. 5 3
      src/Avalonia.Animation/Easing/BounceEaseInOut.cs
  14. 3 3
      src/Avalonia.Animation/Easing/BounceEaseOut.cs
  15. 1 1
      src/Avalonia.Animation/Easing/CircularEaseIn.cs
  16. 1 1
      src/Avalonia.Animation/Easing/CircularEaseInOut.cs
  17. 1 1
      src/Avalonia.Animation/Easing/CircularEaseOut.cs
  18. 1 1
      src/Avalonia.Animation/Easing/CubicEaseIn.cs
  19. 1 1
      src/Avalonia.Animation/Easing/CubicEaseInOut.cs
  20. 1 1
      src/Avalonia.Animation/Easing/CubicEaseOut.cs
  21. 3 2
      src/Avalonia.Animation/Easing/ElasticEaseIn.cs
  22. 4 3
      src/Avalonia.Animation/Easing/ElasticEaseInOut.cs
  23. 3 2
      src/Avalonia.Animation/Easing/ElasticEaseOut.cs
  24. 1 1
      src/Avalonia.Animation/Easing/ExponentialEaseIn.cs
  25. 1 1
      src/Avalonia.Animation/Easing/ExponentialEaseInOut.cs
  26. 1 1
      src/Avalonia.Animation/Easing/ExponentialEaseOut.cs
  27. 1 1
      src/Avalonia.Animation/Easing/LinearEasing.cs
  28. 1 1
      src/Avalonia.Animation/Easing/QuadraticEaseIn.cs
  29. 1 1
      src/Avalonia.Animation/Easing/QuadraticEaseInOut.cs
  30. 1 1
      src/Avalonia.Animation/Easing/QuadraticEaseOut.cs
  31. 1 1
      src/Avalonia.Animation/Easing/QuarticEaseIn.cs
  32. 1 1
      src/Avalonia.Animation/Easing/QuarticEaseInOut.cs
  33. 1 1
      src/Avalonia.Animation/Easing/QuarticEaseOut.cs
  34. 1 1
      src/Avalonia.Animation/Easing/QuinticEaseIn.cs
  35. 1 1
      src/Avalonia.Animation/Easing/QuinticEaseInOut.cs
  36. 1 1
      src/Avalonia.Animation/Easing/QuinticEaseOut.cs
  37. 3 2
      src/Avalonia.Animation/Easing/SineEaseIn.cs
  38. 1 1
      src/Avalonia.Animation/Easing/SineEaseInOut.cs
  39. 3 2
      src/Avalonia.Animation/Easing/SineEaseOut.cs
  40. 17 0
      src/Avalonia.Animation/IAnimation.cs
  41. 1 1
      src/Avalonia.Animation/IEasing.cs
  42. 88 0
      src/Avalonia.Animation/Keyframes/Cue.cs
  43. 87 0
      src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs
  44. 17 0
      src/Avalonia.Animation/Keyframes/IKeyFrames.cs
  45. 80 0
      src/Avalonia.Animation/Keyframes/KeyFrame.cs
  46. 122 0
      src/Avalonia.Animation/Keyframes/KeyFrames.cs
  47. 6 1
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  48. 21 0
      src/Avalonia.Animation/Timing.cs
  49. 1 1
      src/Avalonia.Animation/Transitions/DoubleTransition.cs
  50. 1 1
      src/Avalonia.Animation/Transitions/FloatTransition.cs
  51. 2 2
      src/Avalonia.Animation/Transitions/ITransition.cs
  52. 1 1
      src/Avalonia.Animation/Transitions/IntegerTransition.cs
  53. 4 3
      src/Avalonia.Animation/Transitions/Transition.cs
  54. 1 1
      src/Avalonia.Animation/Transitions/Transitions.cs
  55. 2 2
      src/Avalonia.Animation/Utils/BounceEaseUtils.cs
  56. 2 2
      src/Avalonia.Animation/Utils/DoubleUtils.cs
  57. 2 2
      src/Avalonia.Animation/Utils/EasingUtils.cs
  58. 1 0
      src/Avalonia.Styling/Avalonia.Styling.csproj
  59. 20 1
      src/Avalonia.Styling/Styling/Style.cs
  60. 77 0
      src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs
  61. 2 2
      src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
  62. 2 2
      src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
  63. 2 0
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  64. 1 1
      src/Markup/Avalonia.Markup.Xaml/Converters/EasingTypeConverter.cs
  65. 1 0
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs

+ 2 - 1
samples/RenderTest/MainWindow.xaml

@@ -1,6 +1,7 @@
 <Window xmlns="https://github.com/avaloniaui"
         Title="Avalonia Render Test"
-        xmlns:pages="clr-namespace:RenderTest.Pages;assembly=RenderTest">
+        xmlns:pages="clr-namespace:RenderTest.Pages;assembly=RenderTest" MinWidth="400" MinHeight="200"
+        >
   <DockPanel>
     <Menu DockPanel.Dock="Top">
       <MenuItem Header="Rendering">

+ 70 - 0
samples/RenderTest/Pages/AnimationsPage.xaml

@@ -1,2 +1,72 @@
 <UserControl xmlns="https://github.com/avaloniaui">
+    <Grid>
+        <Grid.Styles>
+            <Styles>
+              <Style Selector="Rectangle.Test">
+                <Setter Property="Fill" Value="Red"/>
+                <Setter Property="Margin" Value="15"/>
+                <Setter Property="Width" Value="100"/>
+                <Setter Property="Height" Value="100"/>
+              </Style>
+              
+              <Style Selector="Rectangle.Rect1:pointerover">
+                <Style.Animations>
+                  <Animation Duration="0:0:2.5" Easing="BounceEaseInOut">
+                    <TransformKeyFrames Property="RotateTransform.Angle">
+                      <KeyFrame Cue="0%" Value="0"/>
+                      <KeyFrame Cue="100%" Value="360"/>
+                    </TransformKeyFrames>
+                  </Animation>
+                </Style.Animations>
+              </Style>
+
+              <Style Selector="Rectangle.Rect2:pointerover">
+                <Style.Animations>
+                  <Animation Duration="0:0:0.5" Easing="SineEaseInOut">
+                    <TransformKeyFrames Property="ScaleTransform.ScaleX">
+                      <KeyFrame Cue="0%" Value="0.8"/>
+                      <KeyFrame Cue="100%" Value="1"/>
+                    </TransformKeyFrames>
+                    <TransformKeyFrames Property="ScaleTransform.ScaleY">
+                      <KeyFrame Cue="0%" Value="0.8"/>
+                      <KeyFrame Cue="100%" Value="1"/>
+                    </TransformKeyFrames>
+                  </Animation>
+                </Style.Animations>
+              </Style>
+
+              <Style Selector="Rectangle.Rect3:pointerover">
+                <Style.Animations>
+                  <Animation Duration="0:0:3" Easing="BounceEaseInOut">
+                    <TransformKeyFrames Property="TranslateTransform.Y">
+                      <KeyFrame Cue="0%" Value="0"/>
+                      <KeyFrame Cue="50%" Value="-100"/>
+                      <KeyFrame Cue="100%" Value="0"/>
+                    </TransformKeyFrames>
+                  </Animation>
+                </Style.Animations>
+              </Style>
+            </Styles>
+        </Grid.Styles>
+      <StackPanel VerticalAlignment="Center"
+                  HorizontalAlignment="Center"
+                  Orientation="Horizontal"
+                  ClipToBounds="False">
+        <Rectangle Classes="Test Rect1">
+          <Rectangle.RenderTransform>
+            <RotateTransform/>
+          </Rectangle.RenderTransform>
+        </Rectangle>
+        <Rectangle Classes="Test Rect2">
+          <Rectangle.RenderTransform>
+            <ScaleTransform ScaleX="0.8" ScaleY="0.8"/>
+          </Rectangle.RenderTransform>
+        </Rectangle>
+        <Rectangle Classes="Test Rect3">
+          <Rectangle.RenderTransform>
+            <TranslateTransform/>
+          </Rectangle.RenderTransform>
+        </Rectangle>
+      </StackPanel>
+    </Grid>
 </UserControl>

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

@@ -15,7 +15,7 @@ namespace RenderTest.Pages
         public AnimationsPage()
         {
             this.InitializeComponent();
-            this.CreateAnimations();
+           // this.CreateAnimations();
         }
 
         private void InitializeComponent()

+ 24 - 151
samples/RenderTest/RenderTest.csproj

@@ -1,18 +1,10 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}</ProjectGuid>
-    <OutputType>WinExe</OutputType>
-    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <RootNamespace>RenderTest</RootNamespace>
     <AssemblyName>RenderTest</AssemblyName>
-    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-    <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
@@ -33,152 +25,33 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
-  <PropertyGroup>
-    <StartupObject />
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Net.Http" />
-    <Reference Include="System.Xml" />
-    <Reference Include="WindowsBase" />
-  </ItemGroup>
   <ItemGroup>
-    <Compile Include="App.xaml.cs">
-      <DependentUpon>App.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\DrawingPage.xaml.cs">
-      <DependentUpon>DrawingPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\ClippingPage.xaml.cs">
-      <DependentUpon>ClippingPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\AnimationsPage.xaml.cs">
-      <DependentUpon>AnimationsPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Program.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="MainWindow.xaml.cs">
-      <DependentUpon>MainWindow.xaml</DependentUpon>
+    <Compile Update="**\*.xaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
     </Compile>
-    <Compile Include="ViewModels\MainWindowViewModel.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="App.config" />
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="App.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
-      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
-      <Name>Avalonia.Animation</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
-      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
-      <Name>Avalonia.Base</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
-      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
-      <Name>Avalonia.Controls</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
-      <Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
-      <Name>Avalonia.DesignerSupport</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
-      <Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
-      <Name>Avalonia.Diagnostics</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
-      <Project>{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}</Project>
-      <Name>Avalonia.DotNetFrameworkRuntime</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
-      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
-      <Name>Avalonia.Input</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
-      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
-      <Name>Avalonia.Interactivity</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
-      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
-      <Name>Avalonia.Layout</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
-      <Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
-      <Name>Avalonia.Logging.Serilog</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
-      <Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
-      <Name>Avalonia.ReactiveUI</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
-      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
-      <Name>Avalonia.Visuals</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
-      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
-      <Name>Avalonia.Styling</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
-      <Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
-      <Name>Avalonia.Themes.Default</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
-      <Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
-      <Name>Avalonia.Markup.Xaml</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
-      <Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
-      <Name>Avalonia.Markup</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
-      <Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
-      <Name>Avalonia.Skia</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj">
-      <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
-      <Name>Avalonia.Direct2D1</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
-      <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
-      <Name>Avalonia.Win32</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="MainWindow.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="SideBar.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Pages\AnimationsPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Pages\ClippingPage.xaml">
+    <EmbeddedResource Include="**\*.xaml">
       <SubType>Designer</SubType>
     </EmbeddedResource>
   </ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="Pages\DrawingPage.xaml">
-      <SubType>Designer</SubType>
-    </EmbeddedResource>
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
+    <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
   </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />

+ 1 - 1
samples/RenderTest/SideBar.xaml

@@ -37,7 +37,7 @@
     <Setter Property="Opacity" Value="0.5"/>
     <Setter Property="Transitions">
       <Transitions>
-        <DoubleTransition Property="Opacity" Easing="CircularEaseIn" Duration="0:0:0.5"/>
+        <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
       </Transitions>
     </Setter>
   </Style>

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

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

+ 4 - 3
src/Avalonia.Animation/Animatable.cs

@@ -6,6 +6,7 @@ using Avalonia.Data;
 using System;
 using System.Reactive.Linq;
 using Avalonia.Collections;
+using Avalonia.Animation.Transitions;
 
 namespace Avalonia.Animation
 {
@@ -17,13 +18,13 @@ namespace Avalonia.Animation
         /// <summary>
         /// 
         /// </summary>
-        public static readonly StyledProperty<AvaloniaList<ITransition>> TransitionsProperty =
-                AvaloniaProperty.Register<Animatable, AvaloniaList<ITransition>>(nameof(Transitions));
+        public static readonly StyledProperty<Transitions.Transitions> TransitionsProperty =
+                AvaloniaProperty.Register<Animatable, Transitions.Transitions>(nameof(Transitions));
 
         /// <summary>
         /// Gets or sets the property transitions for the control.
         /// </summary>
-        public AvaloniaList<ITransition> Transitions
+        public Transitions.Transitions Transitions
         {
             get { return GetValue(TransitionsProperty); }
             set { SetValue(TransitionsProperty, value); }

+ 41 - 24
src/Avalonia.Animation/Animation.cs

@@ -1,55 +1,72 @@
 // 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 Avalonia.Animation.Keyframes;
+using Avalonia.Collections;
+using Avalonia.Metadata;
 using System;
+using System.Collections.Generic;
 
 namespace Avalonia.Animation
 {
     /// <summary>
     /// Tracks the progress of an animation.
     /// </summary>
-    public class Animation : IObservable<object>, IDisposable
+    public class Animation : IDisposable, IAnimation
     {
+        private List<IDisposable>_subscription = new List<IDisposable>();
+
         /// <summary>
-        /// The animation being tracked.
+        /// Run time of this animation.
         /// </summary>
-        private readonly IObservable<object> _inner;
+        public TimeSpan Duration { get; set; }
 
         /// <summary>
-        /// The disposable used to cancel the animation.
+        /// Delay time for animation.
         /// </summary>
-        private readonly IDisposable _subscription;
+        public TimeSpan Delay { get; set; }
+
+        /// <summary>
+        /// Easing function to be used.
+        /// </summary> 
+        public Easing Easing { get; set; } = new LinearEasing();
 
         /// <summary>
-        /// Initializes a new instance of the <see cref="Animation"/> class.
+        /// A list of <see cref="IKeyFrames"/> objects.
         /// </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)
-        {
-            _inner = inner;
-            _subscription = subscription;
-        }
+        [Content]
+        public AvaloniaList<IKeyFrames> Children { get; set; } = new AvaloniaList<IKeyFrames>();
 
         /// <summary>
         /// Cancels the animation.
         /// </summary>
         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);
+            foreach (IKeyFrames keyframes in Children)
+            {
+                _subscription.Add(keyframes.Apply(this, control, matchObs));
+            }
+            return this;
         }
+
+        ///// <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)
+        //{
+        //    return _inner.Subscribe(observer);
+        //}
     }
 }

+ 1 - 1
src/Avalonia.Animation/Easing.cs

@@ -6,7 +6,7 @@ using System.Reflection;
 using System.Linq;
 using System.ComponentModel;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Base class for all Easing classes.

+ 1 - 1
src/Avalonia.Animation/Easing/BackEaseIn.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/BackEaseOut.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 

+ 4 - 2
src/Avalonia.Animation/Easing/BounceEaseIn.cs

@@ -2,7 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
-namespace Avalonia.Animation
+using Avalonia.Animation.Utils;
+
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 
@@ -13,7 +15,7 @@ namespace Avalonia.Animation
         /// <inheritdoc/>
         public override double Ease(double progress)
         {
-            return 1 - BounceEaseHelper.Bounce(1 - progress);
+            return 1 - BounceEaseUtils.Bounce(1 - progress);
         }
 
     }

+ 5 - 3
src/Avalonia.Animation/Easing/BounceEaseInOut.cs

@@ -1,7 +1,9 @@
 // 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
+using Avalonia.Animation.Utils;
+
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 
@@ -15,11 +17,11 @@ namespace Avalonia.Animation
             double p = progress;
             if (p < 0.5d)
             {
-                return 0.5f * (1 - BounceEaseHelper.Bounce(1 - (p * 2)));
+                return 0.5f * (1 - BounceEaseUtils.Bounce(1 - (p * 2)));
             }
             else
             {
-                return 0.5f * BounceEaseHelper.Bounce(p * 2 - 1) + 0.5f;
+                return 0.5f * BounceEaseUtils.Bounce(p * 2 - 1) + 0.5f;
             }
         }
 

+ 3 - 3
src/Avalonia.Animation/Easing/BounceEaseOut.cs

@@ -1,8 +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.Animation.Utils;
 
-
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 
@@ -13,7 +13,7 @@ namespace Avalonia.Animation
         /// <inheritdoc/>
         public override double Ease(double progress)
         {
-            return BounceEaseHelper.Bounce(progress);
+            return BounceEaseUtils.Bounce(progress);
         }
     }
 }

+ 1 - 1
src/Avalonia.Animation/Easing/CircularEaseIn.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/CircularEaseInOut.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/CircularEaseOut.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/CubicEaseIn.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/CubicEaseInOut.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/CubicEaseOut.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 

+ 3 - 2
src/Avalonia.Animation/Easing/ElasticEaseIn.cs

@@ -1,9 +1,10 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 
@@ -15,7 +16,7 @@ namespace Avalonia.Animation
         public override double Ease(double progress)
         {
             double p = progress;
-            return Math.Sin(13d * EasingConstants.HALFPI * p) * Math.Pow(2d, 10d * (p - 1));            
+            return Math.Sin(13d * EasingUtils.HALFPI * p) * Math.Pow(2d, 10d * (p - 1));            
         }
     }
 

+ 4 - 3
src/Avalonia.Animation/Easing/ElasticEaseInOut.cs

@@ -2,8 +2,9 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 
@@ -18,11 +19,11 @@ namespace Avalonia.Animation
 
             if (p < 0.5d)
             {
-                return 0.5d * Math.Sin(13d * EasingConstants.HALFPI * (2d * p)) * Math.Pow(2d, 10d * ((2d * p) - 1d));
+                return 0.5d * Math.Sin(13d * EasingUtils.HALFPI * (2d * p)) * Math.Pow(2d, 10d * ((2d * p) - 1d));
             }
             else
             {
-                return 0.5d * (Math.Sin(-13d * EasingConstants.HALFPI * ((2d * p - 1d) + 1d)) * Math.Pow(2d, -10d * (2d * p - 1d)) + 2d);
+                return 0.5d * (Math.Sin(-13d * EasingUtils.HALFPI * ((2d * p - 1d) + 1d)) * Math.Pow(2d, -10d * (2d * p - 1d)) + 2d);
             }            
         }
 

+ 3 - 2
src/Avalonia.Animation/Easing/ElasticEaseOut.cs

@@ -1,9 +1,10 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 
@@ -15,7 +16,7 @@ namespace Avalonia.Animation
         public override double Ease(double progress)
         {
             double p = progress;
-            return Math.Sin(-13d * EasingConstants.HALFPI * (p + 1)) * Math.Pow(2d, -10d * p) + 1d;
+            return Math.Sin(-13d * EasingUtils.HALFPI * (p + 1)) * Math.Pow(2d, -10d * p) + 1d;
 
         }
 

+ 1 - 1
src/Avalonia.Animation/Easing/ExponentialEaseIn.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/ExponentialEaseInOut.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/ExponentialEaseOut.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/LinearEasing.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Linearly eases a <see cref="double"/> value.

+ 1 - 1
src/Avalonia.Animation/Easing/QuadraticEaseIn.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/QuadraticEaseInOut.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/QuadraticEaseOut.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/QuarticEaseIn.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/QuarticEaseInOut.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/QuarticEaseOut.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/QuinticEaseIn.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/QuinticEaseInOut.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 

+ 1 - 1
src/Avalonia.Animation/Easing/QuinticEaseOut.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 

+ 3 - 2
src/Avalonia.Animation/Easing/SineEaseIn.cs

@@ -1,9 +1,10 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases in a <see cref="double"/> value 
@@ -14,7 +15,7 @@ namespace Avalonia.Animation
         /// <inheritdoc/>
         public override double Ease(double progress)
         {
-            return Math.Sin((progress - 1) * EasingConstants.HALFPI) + 1;
+            return Math.Sin((progress - 1) * EasingUtils.HALFPI) + 1;
         }
     }
 }

+ 1 - 1
src/Avalonia.Animation/Easing/SineEaseInOut.cs

@@ -3,7 +3,7 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases a <see cref="double"/> value 

+ 3 - 2
src/Avalonia.Animation/Easing/SineEaseOut.cs

@@ -1,9 +1,10 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Eases out a <see cref="double"/> value 
@@ -15,7 +16,7 @@ namespace Avalonia.Animation
         /// <inheritdoc/>
         public override double Ease(double progress)
         {
-            return Math.Sin(progress * EasingConstants.HALFPI);
+            return Math.Sin(progress * EasingUtils.HALFPI);
         }
     }
 }

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

+ 1 - 1
src/Avalonia.Animation/IEasing.cs

@@ -1,7 +1,7 @@
 // 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
+namespace Avalonia.Animation.Easings
 {
     /// <summary>
     /// Defines the interface for easing classes.

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

@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Text;
+
+namespace Avalonia.Animation.Keyframes
+{
+    /// <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);
+        }
+    }
+
+}

+ 87 - 0
src/Avalonia.Animation/Keyframes/DoubleKeyFrames.cs

@@ -0,0 +1,87 @@
+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.Keyframes
+{
+    /// <summary>
+    /// Key frames that handles <see cref="double"/> properties.
+    /// </summary>
+    public class DoubleKeyFrames : KeyFrames<double>
+    {
+        /// <inheritdocs/>
+        public override IDisposable DoInterpolation(Animation animation, Animatable control, Dictionary<double, double> sortedkeyValues)
+        {
+            var timer = Timing.GetTimer(animation.Duration, animation.Delay);
+
+
+            var interp = timer.Select(p =>
+                {
+                    // Handle the errors rather naively, for now.
+                    try
+                    {
+                        var x = animation.Easing.Ease(p);
+
+                        // Get a pair of keyframes to make the interpolation.
+                        KeyValuePair<double, double> firstCue, lastCue;
+
+                        firstCue = sortedkeyValues.First();
+                        lastCue = sortedkeyValues.Last();
+
+                        // This should be changed later for a much more efficient one 
+                        if (sortedkeyValues.Count() > 2)
+                        {
+                            bool isWithinRange_Start = DoubleUtils.AboutEqual(x, 0.0) || x > 0.0;
+                            bool isWithinRange_End = DoubleUtils.AboutEqual(x, 1.0) || x < 1.0;
+
+                            if (isWithinRange_Start && isWithinRange_End)
+                            { 
+
+                                firstCue = sortedkeyValues.Where(j => j.Key <= x).Last();
+                                lastCue = sortedkeyValues.Where(j=> j.Key >= firstCue.Key).First();
+                            }
+                            else if (!isWithinRange_Start)
+                            {
+                                firstCue = sortedkeyValues.First();
+                                lastCue = sortedkeyValues.Skip(1).First();
+                            }
+                            else if (!isWithinRange_End)
+                            {
+                                firstCue = sortedkeyValues.Skip(sortedkeyValues.Count() - 1).First();
+                                lastCue = sortedkeyValues.Last();
+                            }
+                            else
+                            {
+                                throw new InvalidOperationException
+                                    ($"Can't find KeyFrames within the specified Easing time {x}");
+                            }
+                        }
+
+                        // Piecewise Linear interpolation, courtesy of wikipedia
+                        var y0 = firstCue.Value;
+                        var x0 = firstCue.Key;
+                        var y1 = lastCue.Value;
+                        var x1 = lastCue.Key;
+                        var y = ((y0 * (x1 - x)) + (y1 * (x - x0))) / x1 - x0;
+
+                        return y;
+                    }
+                    catch (Exception e)
+                    {
+                        Debug.WriteLine(e);
+                        return 1;
+                    }
+                });
+
+
+            return control.Bind(Property, interp.Select(p => (object)p), BindingPriority.Animation);
+        }
+
+
+    }
+}

+ 17 - 0
src/Avalonia.Animation/Keyframes/IKeyFrames.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Animation.Keyframes
+{
+    /// <summary>
+    /// Interface for Keyframe group object
+    /// </summary>
+    public interface IKeyFrames
+    {
+        /// <summary>
+        /// Applies the current KeyFrame group to the specified control.
+        /// </summary>
+        IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch);
+    }
+}

+ 80 - 0
src/Avalonia.Animation/Keyframes/KeyFrame.cs

@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.ComponentModel;
+
+namespace Avalonia.Animation.Keyframes
+{
+
+    /// <summary>
+    /// Stores data regarding a specific key
+    /// point and value in an animation.
+    /// </summary>
+    public class KeyFrame
+    {
+        internal bool timeSpanSet, cueSet;
+
+        private TimeSpan _ktimeSpan;
+        private Cue _kCue;
+
+        /// <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;
+            }
+        }
+
+
+        public object Value { get; set; }
+
+        
+        ///// <summary>
+        ///// Initializes a new instance of the <see cref="KeyFrame"/> class.
+        ///// </summary>
+        //public KeyFrame()
+        //{
+
+        //}
+
+    }
+
+
+
+}

+ 122 - 0
src/Avalonia.Animation/Keyframes/KeyFrames.cs

@@ -0,0 +1,122 @@
+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;
+
+namespace Avalonia.Animation.Keyframes
+{
+    /// <summary>
+    /// Base class for KeyFrames 
+    /// </summary>
+    public abstract class KeyFrames<T> : AvaloniaList<KeyFrame>, IKeyFrames
+    {
+
+        /// <summary>
+        /// Target property.
+        /// </summary>
+        public AvaloniaProperty Property { get; set; }
+
+        /// Enable if the derived class will do the verification of  
+        /// its keyframes.
+        internal bool IsVerfifiedAndConverted;
+
+        /// <inheritdoc/>
+        public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
+        {
+            if(obsMatch == null) return null;
+            
+            if (!IsVerfifiedAndConverted)
+                VerifyKeyFrames(animation, typeof(T));
+
+            return obsMatch
+                .Where(p => p == true)
+                .Subscribe(_ => DoInterpolation(animation, control, ConvertedValues));
+        }
+
+
+        /// <summary>
+        /// Interpolates the given keyframes to the control.
+        /// </summary>
+        public abstract IDisposable DoInterpolation(Animation animation,
+                                                    Animatable control,
+                                                    Dictionary<double, T> keyValues);
+
+        internal Dictionary<double, T> ConvertedValues = new Dictionary<double, T>();
+
+        /// <summary>
+        /// Verifies keyframe value types.
+        /// </summary>
+        private void VerifyKeyFrames(Animation animation, Type type)
+        {
+            var typeConv = TypeDescriptor.GetConverter(type);
+
+            foreach (KeyFrame 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);
+                }
+
+                ConvertedValues.Add(_normalizedCue.CueValue, convertedValue);
+
+            }
+
+            // This can be optional if we ever try to make
+            // the default start and end values to be the
+            // property's prior value.
+            SortKeyFrameCues(ConvertedValues);
+
+            IsVerfifiedAndConverted = true;
+
+        }
+
+        private void SortKeyFrameCues(Dictionary<double, T> convertedValues)
+        {
+            SortKeyFrameCues(convertedValues.ToDictionary((k) => k.Key, (v) => (object)v.Value));
+        }
+
+        internal void SortKeyFrameCues(Dictionary<double, object> convertedValues)
+        {
+            bool hasStartKey, hasEndKey;
+            hasStartKey = hasEndKey = false;
+
+            foreach (var converted in ConvertedValues.Keys)
+            {
+                if (DoubleUtils.AboutEqual(converted, 0.0))
+                {
+                    hasStartKey = true;
+                }
+                else if (DoubleUtils.AboutEqual(converted, 1.0))
+                {
+                    hasEndKey = true;
+                }
+            }
+
+            if (!hasStartKey && !hasEndKey)
+                throw new InvalidOperationException
+                    ($"{this.GetType().Name} must have a starting (0% cue) and ending (100% cue) keyframe.");
+
+            // Sort Cues, in case they don't order it by themselves.
+            ConvertedValues = ConvertedValues.OrderBy(p => p.Key)
+                                             .ToDictionary((k) => k.Key, (v) => v.Value);
+
+        }
+    }
+}

+ 6 - 1
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@@ -3,6 +3,11 @@
 
 using Avalonia.Metadata;
 using System.Reflection;
+using System.Runtime.CompilerServices;
 
 [assembly: AssemblyTitle("Avalonia.Animation")]
-[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
+[assembly: InternalsVisibleTo("Avalonia.Visuals")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Keyframes")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Transitions")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]

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

@@ -88,6 +88,27 @@ namespace Avalonia.Animation
                 .Concat(Observable.Return(1.0));
         }
 
+        /// <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 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, TimeSpan delay)
+        {
+            var startTime = Stopwatch.Elapsed.Ticks + delay.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));
+        }
 
     }
 }

+ 1 - 1
src/Avalonia.Animation/Transitions/DoubleTransition.cs

@@ -5,7 +5,7 @@ using Avalonia.Metadata;
 using System;
 using System.Reactive.Linq;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Transitions
 {
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="double"/> types.

+ 1 - 1
src/Avalonia.Animation/Transitions/FloatTransition.cs

@@ -5,7 +5,7 @@ using Avalonia.Metadata;
 using System;
 using System.Reactive.Linq;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Transitions
 {
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="float"/> types.

+ 2 - 2
src/Avalonia.Animation/ITransition.cs → src/Avalonia.Animation/Transitions/ITransition.cs

@@ -5,10 +5,10 @@ using Avalonia.Metadata;
 using System;
 using System.Reactive.Linq;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Transitions
 {
     /// <summary>
-    /// Interface for Property Transition objects.
+    /// Interface for Transition objects.
     /// </summary>
     public interface ITransition
     {

+ 1 - 1
src/Avalonia.Animation/Transitions/IntegerTransition.cs

@@ -5,7 +5,7 @@ using Avalonia.Metadata;
 using System;
 using System.Reactive.Linq;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Transitions
 {
     /// <summary>
     /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="int"/> types.

+ 4 - 3
src/Avalonia.Animation/Transition.cs → src/Avalonia.Animation/Transitions/Transition.cs

@@ -4,8 +4,9 @@
 using Avalonia.Metadata;
 using System;
 using System.Reactive.Linq;
+using Avalonia.Animation.Easings;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Transitions
 {
     /// <summary>
     /// Defines how a property should be animated using a transition.
@@ -13,7 +14,7 @@ namespace Avalonia.Animation
     public abstract class Transition<T> : ITransition
     {
         private AvaloniaProperty _prop;
-        private IEasing _easing;
+        private Easing _easing;
 
         /// <summary>
         /// Gets the duration of the animation.
@@ -23,7 +24,7 @@ namespace Avalonia.Animation
         /// <summary>
         /// Gets the easing class to be used.
         /// </summary>
-        public IEasing Easing
+        public Easing Easing
         {
             get
             {

+ 1 - 1
src/Avalonia.Animation/Transitions.cs → src/Avalonia.Animation/Transitions/Transitions.cs

@@ -3,7 +3,7 @@
 
 using Avalonia.Collections;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Transitions
 {
     /// <summary>
     /// A collection of <see cref="ITransition"/> definitions.

+ 2 - 2
src/Avalonia.Animation/Helpers/BounceEaseHelper.cs → src/Avalonia.Animation/Utils/BounceEaseUtils.cs

@@ -1,12 +1,12 @@
 // 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
+namespace Avalonia.Animation.Utils
 {
     /// <summary>
     /// Helper static class for BounceEase classes.
     /// </summary>
-    internal static class BounceEaseHelper
+    internal static class BounceEaseUtils
     {
         /// <summary>
         /// Returns the consequent <see cref="double"/> value of

+ 2 - 2
src/Avalonia.Animation/Helpers/DoubleHelper.cs → src/Avalonia.Animation/Utils/DoubleUtils.cs

@@ -5,9 +5,9 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Utils
 {
-    internal static class DoubleHelper
+    internal static class DoubleUtils
     {
         internal static bool AboutEqual(double x, double y)
         {

+ 2 - 2
src/Avalonia.Animation/Helpers/EasingConstants.cs → src/Avalonia.Animation/Utils/EasingUtils.cs

@@ -3,12 +3,12 @@
 
 using System;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Utils
 {
     /// <summary>
     /// Helper static class for easing mathematical constants.
     /// </summary>
-    internal static class EasingConstants
+    internal static class EasingUtils
     {
         /// <summary>
         /// Half of <see cref="Math.PI"/>

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

@@ -34,6 +34,7 @@
   <ItemGroup>
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
     <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+    <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
   </ItemGroup>
   <Import Project="..\..\build\Rx.props" />
 </Project>

+ 20 - 1
src/Avalonia.Styling/Styling/Style.cs

@@ -7,6 +7,7 @@ using System.Collections.Specialized;
 using System.Reactive.Linq;
 using Avalonia.Controls;
 using Avalonia.Metadata;
+using Avalonia.Animation;
 
 namespace Avalonia.Styling
 {
@@ -20,6 +21,8 @@ namespace Avalonia.Styling
         private IResourceNode _parent;
         private IResourceDictionary _resources;
 
+        private IList<IAnimation> _animations;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="Style"/> class.
         /// </summary>
@@ -78,6 +81,17 @@ namespace Avalonia.Styling
         [Content]
         public IList<ISetter> Setters { get; set; } = new List<ISetter>();
 
+        public IList<IAnimation> Animations
+        {
+            get
+            {
+                return _animations ?? (_animations = new List<IAnimation>());
+            }
+            set
+            {
+                _animations = value;
+            }
+        }
         /// <inheritdoc/>
         IResourceNode IResourceNode.ResourceParent => _parent;
 
@@ -91,7 +105,7 @@ namespace Avalonia.Styling
         /// <param name="container">
         /// The control that contains this style. May be null.
         /// </param>
-        public void Attach(IStyleable control, IStyleHost container)
+        void IStyle.Attach(IStyleable control, IStyleHost container)
         {
             if (Selector != null)
             {
@@ -101,6 +115,11 @@ namespace Avalonia.Styling
                 {
                     var subs = GetSubscriptions(control);
 
+                    foreach (var animation in Animations)
+                    {
+                        subs.Add(animation.Apply((Animatable)control, match.ObservableResult));
+                    }
+
                     foreach (var setter in Setters)
                     {
                         var sub = setter.Apply(this, control, match.ObservableResult);

+ 77 - 0
src/Avalonia.Visuals/Animation/Keyframes/TransformKeyFrames.cs

@@ -0,0 +1,77 @@
+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.Media;
+
+namespace Avalonia.Animation.Keyframes
+{
+    /// <summary>
+    /// Key frames that handles <see cref="double"/> properties.
+    /// </summary>
+    public class TransformKeyFrames : KeyFrames<double>
+    {
+        DoubleKeyFrames childKeyFrames;
+        
+        /// <inheritdoc/>
+        public override IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch)
+        {
+            var ctrl = (Visual)control;
+
+            // Check if the AvaloniaProperty is Transform derived.
+            if (typeof(Transform).IsAssignableFrom(Property.OwnerType))
+            {
+                var renderTransformType = ctrl.RenderTransform.GetType();
+
+                // It's only 1 transform object so let's target that.
+                if (renderTransformType == Property.OwnerType)
+                {
+                    var targetTransform = Convert.ChangeType(ctrl.RenderTransform, Property.OwnerType);
+
+                    if (childKeyFrames == null)
+                    {
+                        childKeyFrames = new DoubleKeyFrames();
+
+                        foreach (KeyFrame k in this)
+                        {
+                            childKeyFrames.Add(k);
+                        }
+
+                        childKeyFrames.Property = Property;
+                    }
+
+                    return childKeyFrames.Apply(animation, ctrl.RenderTransform, obsMatch);
+                }
+                if (renderTransformType == typeof(TransformGroup))
+                {
+                    foreach (Transform t in ((TransformGroup)ctrl.RenderTransform).Children)
+                    {
+                        if (renderTransformType == Property.OwnerType)
+                        {
+
+                        }
+                    }
+
+                    // not existing in the transform
+
+                }
+            }
+            else
+            {
+                throw new InvalidProgramException($"Unsupported property {Property}");
+            }
+
+            return null;
+        }
+
+        /// <inheritdocs/>
+        public override IDisposable DoInterpolation(Animation animation, Animatable control, Dictionary<double, double> keyValues)
+        {
+            return Timing.GetTimer(animation.Duration, animation.Delay).Subscribe();
+        }
+    }
+}

+ 2 - 2
src/Avalonia.Visuals/Animation/PointTransition.cs → src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs

@@ -5,10 +5,10 @@ using Avalonia.Metadata;
 using System;
 using System.Reactive.Linq;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Transitions
 {
     /// <summary>
-    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Point"/> types.
+    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Point"/> type.
     /// </summary>  
     public class PointTransition : Transition<Point>
     {

+ 2 - 2
src/Avalonia.Visuals/Animation/ThicknessTransition.cs → src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs

@@ -5,10 +5,10 @@ using Avalonia.Metadata;
 using System;
 using System.Reactive.Linq;
 
-namespace Avalonia.Animation
+namespace Avalonia.Animation.Transitions
 {
     /// <summary>
-    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Thickness"/> types.
+    /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Thickness"/> type.
     /// </summary>  
     public class ThicknessTransition : Transition<Thickness>
     {

+ 2 - 0
src/Avalonia.Visuals/Properties/AssemblyInfo.cs

@@ -8,6 +8,8 @@ using Avalonia.Metadata;
 [assembly: AssemblyTitle("Avalonia.Visuals")]
 [assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests")]
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Transitions")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Keyframes")]
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
 
 [assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Converters/EasingTypeConverter.cs

@@ -1,7 +1,7 @@
 // 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;
+using Avalonia.Animation.Easings;
 using System;
 using System.ComponentModel;
 using System.Globalization;

+ 1 - 0
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs

@@ -13,6 +13,7 @@ using Avalonia.Input;
 using Avalonia.Collections;
 using Avalonia.Controls.Templates;
 using Avalonia.Animation;
+using Avalonia.Animation.Easings;
 
 namespace Avalonia.Markup.Xaml.PortableXaml
 {