Browse Source

Merge branch 'master' into linq-expression-expressionobserver

pull/1667/head
Jeremy Koritzinsky 8 years ago
parent
commit
de1f300c59
  1. 4
      .gitmodules
  2. 90
      Avalonia.sln
  3. 30
      build.cake
  4. 8
      build/Binding.props
  5. 2
      build/ReactiveUI.props
  6. 5
      build/SourceLink.props
  7. 2
      build/XUnit.props
  8. 73
      packages.cake
  9. 2
      parameters.cake
  10. 4
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  11. 4
      samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
  12. BIN
      samples/ControlCatalog/Assets/Fonts/SourceSansPro-Bold.ttf
  13. BIN
      samples/ControlCatalog/Assets/Fonts/SourceSansPro-BoldItalic.ttf
  14. BIN
      samples/ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf
  15. BIN
      samples/ControlCatalog/Assets/Fonts/SourceSansPro-Regular.ttf
  16. 7
      samples/ControlCatalog/ControlCatalog.csproj
  17. 4
      samples/ControlCatalog/MainView.xaml
  18. 4
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  19. 4
      samples/ControlCatalog/Pages/CarouselPage.xaml
  20. 6
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  21. 8
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  22. 16
      samples/ControlCatalog/SideBar.xaml
  23. 1
      samples/Previewer/Previewer.csproj
  24. 13
      samples/RenderTest/App.xaml.cs
  25. 4
      samples/RenderTest/MainWindow.xaml
  26. 123
      samples/RenderTest/Pages/AnimationsPage.xaml
  27. 69
      samples/RenderTest/Pages/AnimationsPage.xaml.cs
  28. 45
      samples/RenderTest/Pages/ClippingPage.xaml
  29. 13
      samples/RenderTest/Pages/ClippingPage.xaml.cs
  30. 24
      samples/RenderTest/Program.cs
  31. 17
      samples/RenderTest/SideBar.xaml
  32. 40
      samples/RenderTest/ViewModels/AnimationsPageViewModel.cs
  33. 2
      samples/RenderTest/ViewModels/MainWindowViewModel.cs
  34. 7
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  35. 4
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  36. 92
      src/Avalonia.Animation/Animatable.cs
  37. 134
      src/Avalonia.Animation/Animate.cs
  38. 157
      src/Avalonia.Animation/Animation.cs
  39. 45
      src/Avalonia.Animation/AnimationExtensions.cs
  40. 56
      src/Avalonia.Animation/Animation`1.cs
  41. 23
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  42. 256
      src/Avalonia.Animation/AnimatorStateMachine`1.cs
  43. 192
      src/Avalonia.Animation/Animator`1.cs
  44. 88
      src/Avalonia.Animation/Cue.cs
  45. 41
      src/Avalonia.Animation/DoubleAnimator.cs
  46. 23
      src/Avalonia.Animation/DoubleTransition.cs
  47. 20
      src/Avalonia.Animation/Easing/BackEaseIn.cs
  48. 31
      src/Avalonia.Animation/Easing/BackEaseInOut.cs
  49. 21
      src/Avalonia.Animation/Easing/BackEaseOut.cs
  50. 21
      src/Avalonia.Animation/Easing/BounceEaseIn.cs
  51. 28
      src/Avalonia.Animation/Easing/BounceEaseInOut.cs
  52. 19
      src/Avalonia.Animation/Easing/BounceEaseOut.cs
  53. 21
      src/Avalonia.Animation/Easing/CircularEaseIn.cs
  54. 29
      src/Avalonia.Animation/Easing/CircularEaseInOut.cs
  55. 22
      src/Avalonia.Animation/Easing/CircularEaseOut.cs
  56. 18
      src/Avalonia.Animation/Easing/CubicEaseIn.cs
  57. 28
      src/Avalonia.Animation/Easing/CubicEaseInOut.cs
  58. 19
      src/Avalonia.Animation/Easing/CubicEaseOut.cs
  59. 56
      src/Avalonia.Animation/Easing/Easing.cs
  60. 13
      src/Avalonia.Animation/Easing/EasingTypeConverter.cs
  61. 22
      src/Avalonia.Animation/Easing/ElasticEaseIn.cs
  62. 31
      src/Avalonia.Animation/Easing/ElasticEaseInOut.cs
  63. 22
      src/Avalonia.Animation/Easing/ElasticEaseOut.cs
  64. 21
      src/Avalonia.Animation/Easing/ExponentialEaseIn.cs
  65. 29
      src/Avalonia.Animation/Easing/ExponentialEaseInOut.cs
  66. 21
      src/Avalonia.Animation/Easing/ExponentialEaseOut.cs
  67. 17
      src/Avalonia.Animation/Easing/LinearEasing.cs
  68. 18
      src/Avalonia.Animation/Easing/QuadraticEaseIn.cs
  69. 27
      src/Avalonia.Animation/Easing/QuadraticEaseInOut.cs
  70. 18
      src/Avalonia.Animation/Easing/QuadraticEaseOut.cs
  71. 19
      src/Avalonia.Animation/Easing/QuarticEaseIn.cs
  72. 30
      src/Avalonia.Animation/Easing/QuarticEaseInOut.cs
  73. 20
      src/Avalonia.Animation/Easing/QuarticEaseOut.cs
  74. 19
      src/Avalonia.Animation/Easing/QuinticEaseIn.cs
  75. 29
      src/Avalonia.Animation/Easing/QuinticEaseInOut.cs
  76. 20
      src/Avalonia.Animation/Easing/QuinticEaseOut.cs
  77. 21
      src/Avalonia.Animation/Easing/SineEaseIn.cs
  78. 20
      src/Avalonia.Animation/Easing/SineEaseInOut.cs
  79. 22
      src/Avalonia.Animation/Easing/SineEaseOut.cs
  80. 14
      src/Avalonia.Animation/FillMode.cs
  81. 23
      src/Avalonia.Animation/FloatTransition.cs
  82. 17
      src/Avalonia.Animation/IAnimation.cs
  83. 8
      src/Avalonia.Animation/IAnimationSetter.cs
  84. 22
      src/Avalonia.Animation/IAnimator.cs
  85. 13
      src/Avalonia.Animation/IEasing.cs
  86. 24
      src/Avalonia.Animation/IEasing`1.cs
  87. 26
      src/Avalonia.Animation/ITransition.cs
  88. 23
      src/Avalonia.Animation/IntegerTransition.cs
  89. 78
      src/Avalonia.Animation/KeyFrame.cs
  90. 41
      src/Avalonia.Animation/KeyFramePair`1.cs
  91. 41
      src/Avalonia.Animation/LinearDoubleEasing.cs
  92. 35
      src/Avalonia.Animation/LinearEasing.cs
  93. 27
      src/Avalonia.Animation/PlayState.cs
  94. 32
      src/Avalonia.Animation/PlaybackDirection.cs
  95. 8
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  96. 50
      src/Avalonia.Animation/PropertyTransition.cs
  97. 202
      src/Avalonia.Animation/RepeatCount.cs
  98. 11
      src/Avalonia.Animation/RepeatCountTypeConverter.cs
  99. 123
      src/Avalonia.Animation/Timing.cs
  100. 68
      src/Avalonia.Animation/Transition`1.cs

4
.gitmodules

@ -1,7 +1,3 @@
[submodule "src/Avalonia.HtmlRenderer/external"]
path = src/Avalonia.HtmlRenderer/external
url = https://github.com/AvaloniaUI/HTML-Renderer.git
branch = perspex-pcl
[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"]
path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
url = https://github.com/AvaloniaUI/Portable.Xaml.git

90
Avalonia.sln

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
@ -15,8 +14,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32", "src\Windo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Direct2D1", "src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj", "{3E908F67-5543-4879-A1DC-08EACE79B3CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer", "src\Windows\Avalonia.Designer\Avalonia.Designer.csproj", "{EC42600F-049B-43FF-AED1-8314D61B2749}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Input", "src\Avalonia.Input\Avalonia.Input.csproj", "{62024B2D-53EB-4638-B26B-85EEAA54866E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Interactivity", "src\Avalonia.Interactivity\Avalonia.Interactivity.csproj", "{6B0ED19D-A08B-461C-A9D9-A9EE40B0C06B}"
@ -68,8 +65,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gtk", "Gtk", "{B9894058-278
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.HtmlRenderer", "src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj", "{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PlatformSupport", "src\Shared\PlatformSupport\PlatformSupport.shproj", "{E4D9629C-F168-4224-3F51-A5E482FFBC42}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Markup\Avalonia.Markup\Avalonia.Markup.csproj", "{6417E941-21BC-467B-A771-0DE389353CE6}"
@ -106,7 +101,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog", "samples\ControlCatalog\ControlCatalog.csproj", "{D0A739B9-3C68-4BA6-A328-41606954B6BD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Desktop", "samples\ControlCatalog.Desktop\ControlCatalog.Desktop.csproj", "{2B888490-D14A-4BCA-AB4B-48676FA93C9B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Desktop", "samples\ControlCatalog.Desktop\ControlCatalog.Desktop.csproj", "{2B888490-D14A-4BCA-AB4B-48676FA93C9B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{57E0455D-D565-44BB-B069-EE1AA20F8337}"
EndProject
@ -134,10 +129,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Gtk3", "src\Gtk\Av
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "samples\ControlCatalog.NetCore\ControlCatalog.NetCore.csproj", "{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{74487168-7D91-487E-BF93-055F2251461E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@ -412,38 +407,6 @@ Global
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|x86.ActiveCfg = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|x86.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Ad-Hoc|NetCoreOnly.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.AppStore|Any CPU.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.AppStore|iPhone.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.AppStore|NetCoreOnly.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.AppStore|x86.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|iPhone.Build.0 = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|x86.ActiveCfg = Debug|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Release|Any CPU.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Release|iPhone.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Release|iPhone.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{EC42600F-049B-43FF-AED1-8314D61B2749}.Release|x86.ActiveCfg = Release|Any CPU
{62024B2D-53EB-4638-B26B-85EEAA54866E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{62024B2D-53EB-4638-B26B-85EEAA54866E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{62024B2D-53EB-4638-B26B-85EEAA54866E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@ -1162,44 +1125,6 @@ Global
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|x86.ActiveCfg = Release|Any CPU
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|x86.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|NetCoreOnly.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|Any CPU.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|iPhone.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|NetCoreOnly.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|x86.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|x86.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|iPhone.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|x86.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|x86.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|iPhone.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|iPhone.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|x86.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|x86.Build.0 = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@ -1739,6 +1664,7 @@ Global
{52F55355-D120-42AC-8116-8410A7D602FA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Debug|x86.ActiveCfg = Debug|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Debug|x86.Build.0 = Debug|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -1748,6 +1674,7 @@ Global
{52F55355-D120-42AC-8116-8410A7D602FA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Release|x86.ActiveCfg = Release|Any CPU
{52F55355-D120-42AC-8116-8410A7D602FA}.Release|x86.Build.0 = Release|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
@ -1775,6 +1702,7 @@ Global
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Debug|x86.ActiveCfg = Debug|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Debug|x86.Build.0 = Debug|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -1784,6 +1712,7 @@ Global
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|x86.ActiveCfg = Release|Any CPU
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|x86.Build.0 = Release|Any CPU
{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
@ -2411,6 +2340,7 @@ Global
{050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.ActiveCfg = Debug|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Debug|x86.Build.0 = Debug|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -2420,6 +2350,7 @@ Global
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.ActiveCfg = Release|Any CPU
{050CC912-FF49-4A8B-B534-9544017446DD}.Release|x86.Build.0 = Release|Any CPU
{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
@ -2545,7 +2476,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{811A76CF-1CF6-440F-963B-BBE31BD72A82} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{3E908F67-5543-4879-A1DC-08EACE79B3CD} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{EC42600F-049B-43FF-AED1-8314D61B2749} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{47ECDF59-DEF8-4C53-87B1-2098A3429059} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{5CCB5571-7C30-4E7D-967D-0E2158EBD91F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{76716382-3159-460E-BDA6-C5715CF606D7} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
@ -2582,8 +2512,6 @@ Global
{7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3}
{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A} = {74487168-7D91-487E-BF93-055F2251461E}
{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}

30
build.cake

@ -93,7 +93,6 @@ Task("Clean")
CleanDirectory(parameters.NugetRoot);
CleanDirectory(parameters.ZipRoot);
CleanDirectory(parameters.BinRoot);
CleanDirectory(parameters.DesignerTestsRoot);
});
Task("Restore-NuGet-Packages")
@ -183,7 +182,7 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
Task("Run-Unit-Tests")
.IsDependentOn("Build")
.IsDependentOn("Run-Designer-Unit-Tests")
.IsDependentOn("Run-Designer-Tests")
.IsDependentOn("Run-Render-Tests")
.WithCriteria(() => !parameters.SkipTests)
.Does(() => {
@ -202,6 +201,13 @@ Task("Run-Unit-Tests")
}
});
Task("Run-Designer-Tests")
.IsDependentOn("Build")
.WithCriteria(() => !parameters.SkipTests)
.Does(() => {
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", parameters, false);
});
Task("Run-Render-Tests")
.IsDependentOn("Build")
.WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
@ -210,25 +216,6 @@ Task("Run-Render-Tests")
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true);
});
Task("Run-Designer-Unit-Tests")
.IsDependentOn("Build")
.WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
.Does(() =>
{
var toolPath = (parameters.IsPlatformAnyCPU || parameters.IsPlatformX86) ?
Context.Tools.Resolve("xunit.console.x86.exe") :
Context.Tools.Resolve("xunit.console.exe");
var xUnitSettings = new XUnit2Settings
{
ToolPath = toolPath,
Parallelism = ParallelismOption.None,
ShadowCopy = false,
};
XUnit2("./artifacts/designer-tests/Avalonia.DesignerSupport.Tests.dll", xUnitSettings);
});
Task("Copy-Files")
.IsDependentOn("Run-Unit-Tests")
.Does(() =>
@ -376,7 +363,6 @@ Task("Inspect")
{
var badIssues = new []{"PossibleNullReferenceException"};
var whitelist = new []{"tests", "src\\android", "src\\ios",
"src\\windows\\avalonia.designer", "src\\avalonia.htmlrenderer\\external",
"src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
Information("Running code inspections");

8
build/Binding.props

@ -0,0 +1,8 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" />
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
<PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
</ItemGroup>
</Project>

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="reactiveui" Version="8.0.0-alpha0073" />
<PackageReference Include="reactiveui" Version="8.0.0" />
</ItemGroup>
</Project>

5
build/SourceLink.props

@ -0,0 +1,5 @@
<Project>
<ItemGroup>
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.0" PrivateAssets="All" />
</ItemGroup>
</Project>

2
build/XUnit.props

@ -8,7 +8,7 @@
<PackageReference Include="xunit.extensibility.execution" Version="2.3.0" />
<PackageReference Include="xunit.runner.console" Version="2.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0" />
<PackageReference Include="Xunit.SkippableFact" Version="1.3.6" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />

73
packages.cake

@ -155,38 +155,28 @@ public class Packages
var coreLibraries = new string[][]
{
new [] { "./src/", "Avalonia.Animation", ".dll" },
new [] { "./src/", "Avalonia.Animation", ".xml" },
new [] { "./src/", "Avalonia.Base", ".dll" },
new [] { "./src/", "Avalonia.Base", ".xml" },
new [] { "./src/", "Avalonia.Controls", ".dll" },
new [] { "./src/", "Avalonia.Controls", ".xml" },
new [] { "./src/", "Avalonia.DesignerSupport", ".dll" },
new [] { "./src/", "Avalonia.DesignerSupport", ".xml" },
new [] { "./src/", "Avalonia.Diagnostics", ".dll" },
new [] { "./src/", "Avalonia.Diagnostics", ".xml" },
new [] { "./src/", "Avalonia.Input", ".dll" },
new [] { "./src/", "Avalonia.Input", ".xml" },
new [] { "./src/", "Avalonia.Interactivity", ".dll" },
new [] { "./src/", "Avalonia.Interactivity", ".xml" },
new [] { "./src/", "Avalonia.Layout", ".dll" },
new [] { "./src/", "Avalonia.Layout", ".xml" },
new [] { "./src/", "Avalonia.Logging.Serilog", ".dll" },
new [] { "./src/", "Avalonia.Logging.Serilog", ".xml" },
new [] { "./src/", "Avalonia.Visuals", ".dll" },
new [] { "./src/", "Avalonia.Visuals", ".xml" },
new [] { "./src/", "Avalonia.Styling", ".dll" },
new [] { "./src/", "Avalonia.Styling", ".xml" },
new [] { "./src/", "Avalonia.Themes.Default", ".dll" },
new [] { "./src/", "Avalonia.Themes.Default", ".xml" },
new [] { "./src/Markup/", "Avalonia.Markup", ".dll" },
new [] { "./src/Markup/", "Avalonia.Markup", ".xml" },
new [] { "./src/Markup/", "Avalonia.Markup.Xaml", ".dll" },
new [] { "./src/Markup/", "Avalonia.Markup.Xaml", ".xml" }
new [] { "./src/", "Avalonia.Animation"},
new [] { "./src/", "Avalonia.Base"},
new [] { "./src/", "Avalonia.Controls"},
new [] { "./src/", "Avalonia.DesignerSupport"},
new [] { "./src/", "Avalonia.Diagnostics"},
new [] { "./src/", "Avalonia.Input"},
new [] { "./src/", "Avalonia.Interactivity"},
new [] { "./src/", "Avalonia.Layout"},
new [] { "./src/", "Avalonia.Logging.Serilog"},
new [] { "./src/", "Avalonia.Visuals"},
new [] { "./src/", "Avalonia.Styling"},
new [] { "./src/", "Avalonia.Themes.Default"},
new [] { "./src/Markup/", "Avalonia.Markup"},
new [] { "./src/Markup/", "Avalonia.Markup.Xaml"},
};
var coreLibrariesFiles = coreLibraries.Select((lib) => {
return (FilePath)context.File(lib[0] + lib[1] + "/bin/" + parameters.DirSuffix + "/netstandard2.0/" + lib[1] + lib[2]);
var extensionsToPack = new [] {".dll", ".xml", ".pdb"};
var coreLibrariesFiles = coreLibraries
.SelectMany(lib => extensionsToPack.Select(ext => new {lib, ext}))
.Select((lib) => {
return (FilePath)context.File(lib.lib[0] + lib.lib[1] + "/bin/" + parameters.DirSuffix + "/netstandard2.0/" + lib.lib[1] + lib.ext);
}).ToList();
var coreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => {
@ -207,16 +197,14 @@ public class Packages
};
});
var net45RuntimePlatformExtensions = new [] {".xml", ".dll"};
var net45RuntimePlatform = net45RuntimePlatformExtensions.Select(libSuffix => {
var net45RuntimePlatform = extensionsToPack.Select(libSuffix => {
return new NuSpecContent {
Source = ((FilePath)context.File("./src/Avalonia.DotNetFrameworkRuntime/bin/" + parameters.DirSuffix + "/net461/Avalonia.DotNetFrameworkRuntime" + libSuffix)).FullPath,
Target = "lib/net45"
};
});
var netCoreRuntimePlatformExtensions = new [] {".xml", ".dll"};
var netCoreRuntimePlatform = netCoreRuntimePlatformExtensions.Select(libSuffix => {
var netCoreRuntimePlatform = extensionsToPack.Select(libSuffix => {
return new NuSpecContent {
Source = ((FilePath)context.File("./src/Avalonia.DotNetCoreRuntime/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.DotNetCoreRuntime" + libSuffix)).FullPath,
Target = "lib/netcoreapp2.0"
@ -274,23 +262,6 @@ public class Packages
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.HtmlRenderer
///////////////////////////////////////////////////////////////////////////////
new NuGetPackSettings()
{
Id = "Avalonia.HtmlRenderer",
Dependencies = new []
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.HtmlRenderer.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.ReactiveUI
///////////////////////////////////////////////////////////////////////////////
new NuGetPackSettings()

2
parameters.cake

@ -30,7 +30,6 @@ public class Parameters
public DirectoryPath NugetRoot { get; private set; }
public DirectoryPath ZipRoot { get; private set; }
public DirectoryPath BinRoot { get; private set; }
public DirectoryPath DesignerTestsRoot { get; private set; }
public string DirSuffix { get; private set; }
public string DirSuffixIOS { get; private set; }
public DirectoryPathCollection BuildDirs { get; private set; }
@ -106,7 +105,6 @@ public class Parameters
NugetRoot = ArtifactsDir.Combine("nuget");
ZipRoot = ArtifactsDir.Combine("zip");
BinRoot = ArtifactsDir.Combine("bin");
DesignerTestsRoot = ArtifactsDir.Combine("designer-tests");
BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");

4
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -111,10 +111,6 @@
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Avalonia.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj">
<Project>{5fb2b005-0a7f-4dad-add4-3ed01444e63d}</Project>
<Name>Avalonia.HtmlRenderer</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Avalonia.Input</Name>

4
samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj

@ -141,10 +141,6 @@
<Project>{7062AE20-5DCC-4442-9645-8195BDECE63E}</Project>
<Name>Avalonia.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj">
<Project>{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}</Project>
<Name>Avalonia.HtmlRenderer</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024B2D-53EB-4638-B26B-85EEAA54866E}</Project>
<Name>Avalonia.Input</Name>

BIN
samples/ControlCatalog/Assets/Fonts/SourceSansPro-Bold.ttf

Binary file not shown.

BIN
samples/ControlCatalog/Assets/Fonts/SourceSansPro-BoldItalic.ttf

Binary file not shown.

BIN
samples/ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf

Binary file not shown.

BIN
samples/ControlCatalog/Assets/Fonts/SourceSansPro-Regular.ttf

Binary file not shown.

7
samples/ControlCatalog/ControlCatalog.csproj

@ -11,6 +11,12 @@
</EmbeddedResource>
<EmbeddedResource Include="Assets\*" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Italic.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Regular.ttf" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
@ -19,7 +25,6 @@
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.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" />

4
samples/ControlCatalog/MainView.xaml

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

4
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -2,12 +2,14 @@ using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Data.Converters;
using Avalonia.Data;
namespace ControlCatalog.Pages
{

4
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"/>
</Button>
<Carousel Name="carousel">
<Carousel.Transition>
<Carousel.PageTransition>
<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.hirsch-899118_640.jpg"/>
<Image Source="resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg"/>

6
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@ -37,13 +37,13 @@ namespace ControlCatalog.Pages
switch (_transition.SelectedIndex)
{
case 0:
_carousel.Transition = null;
_carousel.PageTransition = null;
break;
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;
case 2:
_carousel.Transition = new CrossFade(TimeSpan.FromSeconds(0.25));
_carousel.PageTransition = new CrossFade(TimeSpan.FromSeconds(0.25));
break;
}
}

8
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -31,6 +31,12 @@
<TextBox AcceptsReturn="True" Width="200" Height="125"
Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical" Gap="8">
<TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-Italic.ttf?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-*.ttf?assembly=ControlCatalog#Source Sans Pro"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

16
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">
<Setter Property="Template">
<ControlTemplate>
@ -20,7 +21,7 @@
MemberSelector="{x:Static TabControl.ContentSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex}"
Transition="{TemplateBinding Transition}"
PageTransition="{TemplateBinding PageTransition}"
Grid.Row="1"/>
</DockPanel>
</ControlTemplate>
@ -32,9 +33,20 @@
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="0"/>
<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 Selector="TabControl.sidebar TabStripItem:selected">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
<Setter Property="Opacity" Value="1"/>
</Style>
</Styles>

1
samples/Previewer/Previewer.csproj

@ -15,7 +15,6 @@
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.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" />

13
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.
using Avalonia;
using Avalonia.Logging.Serilog;
using Avalonia.Markup.Xaml;
namespace RenderTest
@ -12,5 +13,17 @@ namespace RenderTest
{
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();
}
}

4
samples/RenderTest/MainWindow.xaml

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

123
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%">
<Setter Property="RotateTransform.Angle" Value="45"/>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="ScaleTransform.ScaleX" Value="1.5"/>
</KeyFrame>
<KeyFrame Cue="80%">
<Setter 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%">
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter 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%">
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter 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%">
<Setter 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%">
<Setter Property="SkewTransform.AngleX" Value="-20"/>
</KeyFrame>
<KeyFrame Cue="75%">
<Setter 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>

69
samples/RenderTest/Pages/AnimationsPage.xaml.cs

@ -7,6 +7,7 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using RenderTest.ViewModels;
namespace RenderTest.Pages
{
@ -14,77 +15,13 @@ namespace RenderTest.Pages
{
public AnimationsPage()
{
this.InitializeComponent();
this.CreateAnimations();
InitializeComponent();
this.DataContext = new AnimationsPageViewModel();
}
private void InitializeComponent()
{
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;
}
}
}

45
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">
<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%">
<Setter Property="RotateTransform.Angle" Value="360"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles>
</Grid.Styles>
<Border Name="clipped"
Background="Yellow"
Width="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">
<!-- Setting opacity puts the TextBox on a new layer -->
<TextBox Text="Avalonia" Opacity="0.9" VerticalAlignment="Center"/>
<Border.RenderTransform>
<RotateTransform/>
</Border.RenderTransform>
</Border>
</Border>
<CheckBox Name="useMask" IsChecked="True" Grid.Row="1">Apply Geometry Clip</CheckBox>

13
samples/RenderTest/Pages/ClippingPage.xaml.cs

@ -16,7 +16,6 @@ namespace RenderTest.Pages
public ClippingPage()
{
InitializeComponent();
CreateAnimations();
WireUpCheckbox();
}
@ -25,18 +24,6 @@ namespace RenderTest.Pages
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()
{
var useMask = this.FindControl<CheckBox>("useMask");

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

17
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">
<Setter Property="Template">
<ControlTemplate>
@ -20,7 +22,7 @@
MemberSelector="{x:Static TabControl.ContentSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex}"
Transition="{TemplateBinding Transition}"
PageTransition="{TemplateBinding PageTransition}"
Grid.Row="1"/>
</DockPanel>
</ControlTemplate>
@ -32,9 +34,20 @@
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="0"/>
<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 Selector="TabControl.sidebar TabStripItem:selected">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
<Setter Property="Opacity" Value="1"/>
</Style>
</Styles>

40
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; }
}
}

2
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()

7
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@ -40,11 +40,14 @@ namespace Avalonia.Android
public partial class String
{
// aapt resource value: 0x7f020002
public static int ApplicationName = 2130837506;
// aapt resource value: 0x7f020001
public static int ApplicationName = 2130837505;
public static int Hello = 2130837505;
// aapt resource value: 0x7f020000
public static int Hello = 2130837504;
public static int library_name = 2130837504;
static String()
{

4
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -143,10 +143,6 @@
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Avalonia.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj">
<Project>{5fb2b005-0a7f-4dad-add4-3ed01444e63d}</Project>
<Name>Avalonia.HtmlRenderer</Name>
</ProjectReference>
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
<Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
<Name>Avalonia.Skia</Name>

92
src/Avalonia.Animation/Animatable.cs

@ -3,6 +3,13 @@
using System.Linq;
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
{
@ -12,45 +19,90 @@ namespace Avalonia.Animation
public class Animatable : AvaloniaObject
{
/// <summary>
/// The property transitions for the control.
/// Initializes this <see cref="Animatable"/> object.
/// </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>
/// Gets or sets the property transitions for the control.
/// The specific animations timer for this control.
/// </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>
/// 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>
/// <param name="e">The event args.</param>
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)
{
Animate.Property(this, e.Property, e.OldValue, e.NewValue, match.Easing, match.Duration);
match.Apply(this, e.OldValue, e.NewValue);
}
}
}
}
}

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

157
src/Avalonia.Animation/Animation.cs

@ -1,34 +1,139 @@
// 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;
using Avalonia.Collections;
using Avalonia.Metadata;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Tracks the progress of an animation.
/// </summary>
public class Animation : IObservable<object>, IDisposable
public class Animation : AvaloniaList<KeyFrame>, IDisposable, IAnimation
{
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
{
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
};
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator: IAnimator
{
Animators.Insert(0, (condition, typeof(TAnimator)));
}
private static Type GetAnimatorType(AvaloniaProperty property)
{
foreach (var (condition, type) in Animators)
{
if (condition(property))
{
return type;
}
}
return null;
}
private bool _isChildrenChanged = false;
private List<IDisposable> _subscription = new List<IDisposable>();
public AvaloniaList<IAnimator> _animators { get; set; } = new AvaloniaList<IAnimator>();
/// <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 this animation.
/// </summary>
private readonly IDisposable _subscription;
public TimeSpan Delay { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Animation"/> class.
/// The repeat count for this animation.
/// </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 handler = GetAnimatorType(setter.Property);
if (handler == null)
{
throw new InvalidOperationException($"No animator registered for the property {setter.Property}. Add an animator to the Animation.Animators collection that matches this property to animate it.");
}
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>
@ -36,20 +141,26 @@ namespace Avalonia.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);
if (_isChildrenChanged)
{
InterpretKeyframes();
_isChildrenChanged = false;
}
foreach (IAnimator keyframes in _animators)
{
_subscription.Add(keyframes.Apply(this, control, matchObs));
}
return this;
}
}
}
}

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

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

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

23
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
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
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
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
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
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
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
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
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
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
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
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
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
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.");
}
}
}
}

13
src/Markup/Avalonia.Markup.Xaml/Converters/ColorTypeConverter.cs → src/Avalonia.Animation/Easing/EasingTypeConverter.cs

@ -1,16 +1,14 @@
// 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.Globalization;
using Avalonia.Media;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
namespace Avalonia.Animation.Easings
{
public class ColorTypeConverter : TypeConverter
public class EasingTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
@ -19,8 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Color.Parse((string)value);
return Easing.Parse((string)value);
}
}
}

22
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
src/Avalonia.Animation/IAnimationSetter.cs

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

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

13
src/Avalonia.Animation/IEasing.cs

@ -1,23 +1,16 @@
// 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 functions.
/// Defines the interface for easing classes.
/// </summary>
public interface 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>
object Ease(double progress, object start, object finish);
double Ease(double progress);
}
}

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

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

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
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
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
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")]

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

11
src/Markup/Avalonia.Markup.Xaml/Converters/ClassesTypeConverter.cs → src/Avalonia.Animation/RepeatCountTypeConverter.cs

@ -1,13 +1,14 @@
// 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.Globalization;
using Avalonia.Controls;
using System.ComponentModel;
namespace Avalonia.Markup.Xaml.Converters
using System.Globalization;
namespace Avalonia.Animation
{
public class ClassesTypeConverter : TypeConverter
public class RepeatCountTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
@ -16,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return new Classes(((string)value).Split(' '));
return RepeatCount.Parse((string)value);
}
}
}

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save