Browse Source

Merge branch 'master' into feature/SkiaTypefaceFallback

pull/2162/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
3f127d9da6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      Avalonia.sln
  2. 21
      native/Avalonia.Native/src/OSX/window.mm
  3. 1
      nukebuild/Build.cs
  4. 35
      samples/RenderDemo/Pages/AnimationsPage.xaml
  5. 2
      samples/RenderDemo/Pages/ClippingPage.xaml
  6. 1
      src/Avalonia.Animation/Animatable.cs
  7. 199
      src/Avalonia.Animation/Animation.cs
  8. 204
      src/Avalonia.Animation/AnimationInstance`1.cs
  9. 73
      src/Avalonia.Animation/Animators/Animator`1.cs
  10. 21
      src/Avalonia.Animation/Animators/BoolAnimator.cs
  11. 24
      src/Avalonia.Animation/Animators/ByteAnimator.cs
  12. 17
      src/Avalonia.Animation/Animators/DecimalAnimator.cs
  13. 17
      src/Avalonia.Animation/Animators/DoubleAnimator.cs
  14. 17
      src/Avalonia.Animation/Animators/FloatAnimator.cs
  15. 24
      src/Avalonia.Animation/Animators/Int16Animator.cs
  16. 24
      src/Avalonia.Animation/Animators/Int32Animator.cs
  17. 24
      src/Avalonia.Animation/Animators/Int64Animator.cs
  18. 24
      src/Avalonia.Animation/Animators/UInt16Animator.cs
  19. 24
      src/Avalonia.Animation/Animators/UInt32Animator.cs
  20. 24
      src/Avalonia.Animation/Animators/UInt64Animator.cs
  21. 3
      src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs
  22. 35
      src/Avalonia.Animation/DoubleAnimator.cs
  23. 0
      src/Avalonia.Animation/Easing/IEasing.cs
  24. 176
      src/Avalonia.Animation/IterationCount.cs
  25. 4
      src/Avalonia.Animation/IterationCountTypeConverter.cs
  26. 15
      src/Avalonia.Animation/KeyFrame.cs
  27. 33
      src/Avalonia.Animation/KeyFramePair`1.cs
  28. 33
      src/Avalonia.Animation/KeyFrames.cs
  29. 3
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  30. 199
      src/Avalonia.Animation/RepeatCount.cs
  31. 7
      src/Avalonia.Animation/Transitions/DoubleTransition.cs
  32. 0
      src/Avalonia.Animation/Transitions/FloatTransition.cs
  33. 0
      src/Avalonia.Animation/Transitions/IntegerTransition.cs
  34. 1
      src/Avalonia.Controls/Panel.cs
  35. 2
      src/Avalonia.Native/PlatformThreadingInterface.cs
  36. 4
      src/Avalonia.Themes.Default/ProgressBar.xaml
  37. 70
      src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs
  38. 27
      src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs
  39. 17
      src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs
  40. 23
      src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs
  41. 17
      src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs
  42. 74
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  43. 17
      src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs
  44. 37
      src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
  45. 17
      src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs
  46. 44
      src/Avalonia.Visuals/Animation/CrossFade.cs
  47. 89
      src/Avalonia.Visuals/Animation/PageSlide.cs
  48. 36
      src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
  49. 7
      src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
  50. 25
      src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
  51. 13
      src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
  52. 25
      src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
  53. 6
      src/Avalonia.Visuals/CornerRadius.cs
  54. 3
      src/Avalonia.Visuals/Media/Brush.cs
  55. 7
      src/Avalonia.Visuals/Media/Color.cs
  56. 3
      src/Avalonia.Visuals/Media/SolidColorBrush.cs
  57. 1
      src/Avalonia.Visuals/Media/Transform.cs
  58. 14
      src/Avalonia.Visuals/Point.cs
  59. 6
      src/Avalonia.Visuals/Rect.cs
  60. 6
      src/Avalonia.Visuals/Size.cs
  61. 39
      src/Avalonia.Visuals/Thickness.cs
  62. 10
      src/Avalonia.Visuals/Vector.cs
  63. 2
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  64. 77
      tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs
  65. 25
      tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj
  66. 10
      tests/Avalonia.Animation.UnitTests/Properties/AssemblyInfo.cs
  67. 28
      tests/Avalonia.Animation.UnitTests/TestClock.cs

29
Avalonia.sln

@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.27130.2027 VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
@ -193,6 +193,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}"
EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -1720,6 +1722,30 @@ Global
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|Any CPU.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1773,6 +1799,7 @@ Global
{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{D49233F8-F29C-47DD-9975-C4C9E4502720} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C} {D49233F8-F29C-47DD-9975-C4C9E4502720} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{3C471044-3640-45E3-B1B2-16D2FF8399EE} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C} {3C471044-3640-45E3-B1B2-16D2FF8399EE} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{AF227847-E65C-4BE9-BCE9-B551357788E0} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

21
native/Avalonia.Native/src/OSX/window.mm

@ -425,7 +425,7 @@ private:
[[Window parentWindow] removeChildWindow:Window]; [[Window parentWindow] removeChildWindow:Window];
WindowBaseImpl::Show(); WindowBaseImpl::Show();
return SetWindowState(_lastWindowState); return SetWindowState(Normal);
} }
} }
@ -1184,6 +1184,25 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
} }
} }
- (void)windowDidMiniaturize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (void)windowDidDeminiaturize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
{ {

1
nukebuild/Build.cs

@ -124,6 +124,7 @@ partial class Build : NukeBuild
.DependsOn(Compile) .DependsOn(Compile)
.Executes(() => .Executes(() =>
{ {
RunCoreTest("./tests/Avalonia.Animation.UnitTests", false);
RunCoreTest("./tests/Avalonia.Base.UnitTests", false); RunCoreTest("./tests/Avalonia.Base.UnitTests", false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", false); RunCoreTest("./tests/Avalonia.Controls.UnitTests", false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", false); RunCoreTest("./tests/Avalonia.Input.UnitTests", false);

35
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -43,7 +43,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="Border.Rect1:pointerover"> <Style Selector="Border.Rect1:pointerover">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:2.5" <Animation Duration="0:0:2.5"
RepeatCount="4" IterationCount="4"
FillMode="None" FillMode="None"
PlaybackDirection="AlternateReverse" PlaybackDirection="AlternateReverse"
Easing="SineEaseInOut"> Easing="SineEaseInOut">
@ -73,7 +73,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:0.5" <Animation Duration="0:0:0.5"
Easing="QuadraticEaseInOut" Easing="QuadraticEaseInOut"
RepeatCount="Loop"> IterationCount="Infinite">
<KeyFrame Cue="50%"> <KeyFrame Cue="50%">
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/> <Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/> <Setter Property="ScaleTransform.ScaleY" Value="0.8"/>
@ -87,6 +87,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Animation Duration="0:0:3" Easing="BounceEaseInOut"> <Animation Duration="0:0:3" Easing="BounceEaseInOut">
<KeyFrame Cue="48%"> <KeyFrame Cue="48%">
<Setter Property="TranslateTransform.Y" Value="-100"/> <Setter Property="TranslateTransform.Y" Value="-100"/>
<Setter Property="Background" Value="Magenta"/>
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
@ -103,6 +104,35 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="Border.Rect6">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="Red"/>
</KeyFrame>
<KeyFrame Cue="15%">
<Setter Property="Background" Value="Yellow"/>
</KeyFrame>
<KeyFrame Cue="30%">
<Setter Property="Background" Value="Green"/>
</KeyFrame>
<KeyFrame Cue="45%">
<Setter Property="Background" Value="Cyan"/>
</KeyFrame>
<KeyFrame Cue="60%">
<Setter Property="Background" Value="Blue"/>
</KeyFrame>
<KeyFrame Cue="75%">
<Setter Property="Background" Value="Indigo"/>
</KeyFrame>
<KeyFrame Cue="90%">
<Setter Property="Background" Value="Violet"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles> </Styles>
</UserControl.Styles> </UserControl.Styles>
<Grid> <Grid>
@ -120,6 +150,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border Classes="Test Rect3"/> <Border Classes="Test Rect3"/>
<Border Classes="Test Rect4" Background="Navy"/> <Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/> <Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Red"/>
</WrapPanel> </WrapPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

2
samples/RenderDemo/Pages/ClippingPage.xaml

@ -8,7 +8,7 @@ xmlns="https://github.com/avaloniaui">
</Style> </Style>
<Style Selector="Border#clipChild"> <Style Selector="Border#clipChild">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:2" RepeatCount="Loop"> <Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="360"/> <Setter Property="RotateTransform.Angle" Value="360"/>
</KeyFrame> </KeyFrame>

1
src/Avalonia.Animation/Animatable.cs

@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {

199
src/Avalonia.Animation/Animation.cs

@ -7,68 +7,209 @@ using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Metadata;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// Tracks the progress of an animation. /// Tracks the progress of an animation.
/// </summary> /// </summary>
public class Animation : AvaloniaList<KeyFrame>, IAnimation public class Animation : AvaloniaObject, IAnimation
{ {
/// <summary>
/// Defines the <see cref="Duration"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DurationProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_duration),
o => o._duration,
(o, v) => o._duration = v);
/// <summary>
/// Defines the <see cref="IterationCount"/> property.
/// </summary>
public static readonly DirectProperty<Animation, IterationCount> IterationCountProperty =
AvaloniaProperty.RegisterDirect<Animation, IterationCount>(
nameof(_iterationCount),
o => o._iterationCount,
(o, v) => o._iterationCount = v);
/// <summary>
/// Defines the <see cref="PlaybackDirection"/> property.
/// </summary>
public static readonly DirectProperty<Animation, PlaybackDirection> PlaybackDirectionProperty =
AvaloniaProperty.RegisterDirect<Animation, PlaybackDirection>(
nameof(_playbackDirection),
o => o._playbackDirection,
(o, v) => o._playbackDirection = v);
/// <summary>
/// Defines the <see cref="FillMode"/> property.
/// </summary>
public static readonly DirectProperty<Animation, FillMode> FillModeProperty =
AvaloniaProperty.RegisterDirect<Animation, FillMode>(
nameof(_fillMode),
o => o._fillMode,
(o, v) => o._fillMode = v);
/// <summary>
/// Defines the <see cref="Easing"/> property.
/// </summary>
public static readonly DirectProperty<Animation, Easing> EasingProperty =
AvaloniaProperty.RegisterDirect<Animation, Easing>(
nameof(_easing),
o => o._easing,
(o, v) => o._easing = v);
/// <summary>
/// Defines the <see cref="Delay"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DelayProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_delay),
o => o._delay,
(o, v) => o._delay = v);
/// <summary>
/// Defines the <see cref="DelayBetweenIterations"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DelayBetweenIterationsProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_delayBetweenIterations),
o => o._delayBetweenIterations,
(o, v) => o._delayBetweenIterations = v);
/// <summary>
/// Defines the <see cref="SpeedRatio"/> property.
/// </summary>
public static readonly DirectProperty<Animation, double> SpeedRatioProperty =
AvaloniaProperty.RegisterDirect<Animation, double>(
nameof(_speedRatio),
o => o._speedRatio,
(o, v) => o._speedRatio = v,
defaultBindingMode: BindingMode.TwoWay);
private TimeSpan _duration;
private IterationCount _iterationCount = new IterationCount(1);
private PlaybackDirection _playbackDirection;
private FillMode _fillMode;
private Easing _easing = new LinearEasing();
private TimeSpan _delay = TimeSpan.Zero;
private TimeSpan _delayBetweenIterations = TimeSpan.Zero;
private double _speedRatio = 1d;
/// <summary> /// <summary>
/// Gets or sets the active time of this animation. /// Gets or sets the active time of this animation.
/// </summary> /// </summary>
public TimeSpan Duration { get; set; } public TimeSpan Duration
{
get { return _duration; }
set { SetAndRaise(DurationProperty, ref _duration, value); }
}
/// <summary> /// <summary>
/// Gets or sets the repeat count for this animation. /// Gets or sets the repeat count for this animation.
/// </summary> /// </summary>
public RepeatCount RepeatCount { get; set; } public IterationCount IterationCount
{
get { return _iterationCount; }
set { SetAndRaise(IterationCountProperty, ref _iterationCount, value); }
}
/// <summary> /// <summary>
/// Gets or sets the playback direction for this animation. /// Gets or sets the playback direction for this animation.
/// </summary> /// </summary>
public PlaybackDirection PlaybackDirection { get; set; } public PlaybackDirection PlaybackDirection
{
get { return _playbackDirection; }
set { SetAndRaise(PlaybackDirectionProperty, ref _playbackDirection, value); }
}
/// <summary> /// <summary>
/// Gets or sets the value fill mode for this animation. /// Gets or sets the value fill mode for this animation.
/// </summary> /// </summary>
public FillMode FillMode { get; set; } public FillMode FillMode
{
get { return _fillMode; }
set { SetAndRaise(FillModeProperty, ref _fillMode, value); }
}
/// <summary> /// <summary>
/// Gets or sets the easing function to be used for this animation. /// Gets or sets the easing function to be used for this animation.
/// </summary> /// </summary>
public Easing Easing { get; set; } = new LinearEasing(); public Easing Easing
{
/// <summary> get { return _easing; }
/// Gets or sets the speed multiple for this animation. set { SetAndRaise(EasingProperty, ref _easing, value); }
/// </summary> }
public double SpeedRatio { get; set; } = 1d;
/// <summary> /// <summary>
/// Gets or sets the delay time for this animation. /// Gets or sets the initial delay time for this animation.
/// </summary> /// </summary>
/// <remarks> public TimeSpan Delay
/// Describes a delay to be added before the animation starts, and optionally between {
/// repeats of the animation if <see cref="DelayBetweenIterations"/> is set. get { return _delay; }
/// </remarks> set { SetAndRaise(DelayProperty, ref _delay, value); }
public TimeSpan Delay { get; set; } }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether <see cref="Delay"/> will be applied between /// Gets or sets the delay time in between iterations.
/// iterations of the animation. /// </summary>
public TimeSpan DelayBetweenIterations
{
get { return _delayBetweenIterations; }
set { SetAndRaise(DelayBetweenIterationsProperty, ref _delayBetweenIterations, value); }
}
/// <summary>
/// Gets or sets the speed multiple for this animation.
/// </summary> /// </summary>
/// <remarks> public double SpeedRatio
/// If this property is not set, then <see cref="Delay"/> will only be applied to the first {
/// iteration of the animation. get { return _speedRatio; }
/// </remarks> set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); }
public bool DelayBetweenIterations { get; set; } }
/// <summary>
/// Obsolete: Do not use this property, use <see cref="IterationCount"/> instead.
/// </summary>
/// <value></value>
[Obsolete("This property has been superceded by IterationCount.")]
public string RepeatCount
{
get { return IterationCount.ToString(); }
set
{
var val = value.ToUpper();
val = val.Replace("LOOP", "INFINITE");
val = val.Replace("NONE", "1");
IterationCount = IterationCount.Parse(val);
}
}
/// <summary>
/// Gets the children of the <see cref="Animation"/>.
/// </summary>
[Content]
public KeyFrames Children { get; } = new KeyFrames();
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)> 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) ) ( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator) ),
( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator) ),
( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator) ),
( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator) ),
( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator) ),
( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator) ),
( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator) ),
( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator) ),
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ),
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ),
}; };
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition) public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
@ -95,9 +236,9 @@ namespace Avalonia.Animation
var animatorKeyFrames = new List<AnimatorKeyFrame>(); var animatorKeyFrames = new List<AnimatorKeyFrame>();
var subscriptions = new List<IDisposable>(); var subscriptions = new List<IDisposable>();
foreach (var keyframe in this) foreach (var keyframe in Children)
{ {
foreach (var setter in keyframe) foreach (var setter in keyframe.Setters)
{ {
var handler = GetAnimatorType(setter.Property); var handler = GetAnimatorType(setter.Property);
@ -179,7 +320,7 @@ namespace Avalonia.Animation
{ {
var run = new TaskCompletionSource<object>(); var run = new TaskCompletionSource<object>();
if (this.RepeatCount == RepeatCount.Loop) if (this.IterationCount == IterationCount.Infinite)
run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null; IDisposable subscriptions = null;

204
src/Avalonia.Animation/AnimationInstance`1.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils; using Avalonia.Animation.Utils;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive; using Avalonia.Reactive;
@ -15,78 +16,77 @@ namespace Avalonia.Animation
{ {
private T _lastInterpValue; private T _lastInterpValue;
private T _firstKFValue; private T _firstKFValue;
private long _repeatCount; private ulong? _iterationCount;
private double _currentIteration; private ulong _currentIteration;
private bool _isLooping;
private bool _gotFirstKFValue; private bool _gotFirstKFValue;
private bool _iterationDelay;
private FillMode _fillMode; private FillMode _fillMode;
private PlaybackDirection _animationDirection; private PlaybackDirection _playbackDirection;
private Animator<T> _parent; private Animator<T> _animator;
private Animation _animation;
private Animatable _targetControl; private Animatable _targetControl;
private T _neutralValue; private T _neutralValue;
private double _speedRatio; private double _speedRatioConv;
private TimeSpan _delay; private TimeSpan _initialDelay;
private TimeSpan _iterationDelay;
private TimeSpan _duration; private TimeSpan _duration;
private Easings.Easing _easeFunc; private Easings.Easing _easeFunc;
private Action _onCompleteAction; private Action _onCompleteAction;
private Func<double, T, T> _interpolator; private Func<double, T, T> _interpolator;
private IDisposable _timerSubscription; private IDisposable _timerSub;
private readonly IClock _baseClock; private readonly IClock _baseClock;
private IClock _clock; private IClock _clock;
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator) public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator)
{ {
if (animation.SpeedRatio <= 0) _animator = animator;
throw new InvalidOperationException("Speed ratio cannot be negative or zero."); _animation = animation;
_targetControl = control;
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
_neutralValue = (T)_targetControl.GetValue(_animator.Property);
if (animation.Duration.TotalSeconds <= 0) FetchProperties();
throw new InvalidOperationException("Duration cannot be negative or zero."); }
_parent = animator; private void FetchProperties()
_easeFunc = animation.Easing; {
_targetControl = control; if (_animation.SpeedRatio < 0d)
_neutralValue = (T)_targetControl.GetValue(_parent.Property); throw new ArgumentOutOfRangeException("SpeedRatio value should not be negative.");
_speedRatio = animation.SpeedRatio; if (_animation.Duration.TotalSeconds <= 0)
throw new InvalidOperationException("Duration value cannot be negative or zero.");
_delay = animation.Delay; _easeFunc = _animation.Easing;
_duration = animation.Duration;
_iterationDelay = animation.DelayBetweenIterations;
switch (animation.RepeatCount.RepeatType) _speedRatioConv = 1d / _animation.SpeedRatio;
{
case RepeatType.None:
_repeatCount = 1;
break;
case RepeatType.Loop:
_isLooping = true;
break;
case RepeatType.Repeat:
_repeatCount = (long)animation.RepeatCount.Value;
break;
}
_animationDirection = animation.PlaybackDirection; _initialDelay = _animation.Delay;
_fillMode = animation.FillMode; _duration = _animation.Duration;
_onCompleteAction = OnComplete; _iterationDelay = _animation.DelayBetweenIterations;
_interpolator = Interpolator;
_baseClock = baseClock; if (_animation.IterationCount.RepeatType == IterationType.Many)
} _iterationCount = _animation.IterationCount.Value;
else
_iterationCount = null;
_playbackDirection = _animation.PlaybackDirection;
_fillMode = _animation.FillMode;
}
protected override void Unsubscribed() protected override void Unsubscribed()
{ {
//Animation may have been stopped before it has finished // Animation may have been stopped before it has finished.
ApplyFinalFill(); ApplyFinalFill();
_timerSubscription?.Dispose(); _timerSub?.Dispose();
_clock.PlayState = PlayState.Stop; _clock.PlayState = PlayState.Stop;
} }
protected override void Subscribed() protected override void Subscribed()
{ {
_clock = new Clock(_baseClock); _clock = new Clock(_baseClock);
_timerSubscription = _clock.Subscribe(Step); _timerSub = _clock.Subscribe(Step);
} }
public void Step(TimeSpan frameTick) public void Step(TimeSpan frameTick)
@ -104,7 +104,7 @@ namespace Avalonia.Animation
private void ApplyFinalFill() private void ApplyFinalFill()
{ {
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); _targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue);
} }
private void DoComplete() private void DoComplete()
@ -130,7 +130,7 @@ namespace Avalonia.Animation
if (!_gotFirstKFValue) if (!_gotFirstKFValue)
{ {
_firstKFValue = (T)_parent.First().Value; _firstKFValue = (T)_animator.First().Value;
_gotFirstKFValue = true; _gotFirstKFValue = true;
} }
} }
@ -138,75 +138,77 @@ namespace Avalonia.Animation
private void InternalStep(TimeSpan time) private void InternalStep(TimeSpan time)
{ {
DoPlayStates(); DoPlayStates();
var delayEndpoint = _delay;
var iterationEndpoint = delayEndpoint + _duration;
var iterationTime = time;
//determine if time is currently in the first iteration. FetchProperties();
if (time >= TimeSpan.Zero & time <= iterationEndpoint)
{
_currentIteration = 1;
}
else if (time > iterationEndpoint)
{
//Subtract first iteration to properly get the subsequent iteration time
iterationTime -= iterationEndpoint;
if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) // Scale timebases according to speedratio.
{ var indexTime = time.Ticks;
delayEndpoint = TimeSpan.Zero; var iterDuration = _duration.Ticks * _speedRatioConv;
iterationEndpoint = _duration; var iterDelay = _iterationDelay.Ticks * _speedRatioConv;
} var initDelay = _initialDelay.Ticks * _speedRatioConv;
//Calculate the current iteration number
_currentIteration = Math.Min(_repeatCount,(int)Math.Floor((double)((double)iterationTime.Ticks / iterationEndpoint.Ticks)) + 2);
}
else
{
return;
}
// Determine if the current iteration should have its normalized time inverted. if (indexTime > 0 & indexTime <= initDelay)
bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false :
_animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true :
_animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false :
_animationDirection == PlaybackDirection.Reverse ? true : false;
if (!_isLooping)
{
var totalTime = _iterationDelay ? _repeatCount * ( _duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks;
if (time.Ticks >= totalTime)
{
var easedTime = _easeFunc.Ease(isCurIterReverse ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
DoComplete();
return;
}
}
iterationTime = TimeSpan.FromTicks((long)(iterationTime.Ticks % iterationEndpoint.Ticks));
if (delayEndpoint > TimeSpan.Zero & iterationTime < delayEndpoint)
{ {
DoDelay(); DoDelay();
} }
else else
{ {
// Offset the delay time // Calculate timebases.
iterationTime -= delayEndpoint; var iterationTime = iterDuration + iterDelay;
iterationEndpoint -= delayEndpoint; var opsTime = indexTime - initDelay;
var playbackTime = opsTime % iterationTime;
// Normalize time _currentIteration = (ulong)(opsTime / iterationTime);
var interpVal = (double)iterationTime.Ticks / iterationEndpoint.Ticks;
if (isCurIterReverse) // Stop animation when the current iteration is beyond the iteration count.
interpVal = 1 - interpVal; if ((_currentIteration + 1) > _iterationCount)
DoComplete();
// Ease and interpolate if (playbackTime <= iterDuration)
var easedTime = _easeFunc.Ease(interpVal); {
_lastInterpValue = _interpolator(easedTime, _neutralValue); // Normalize time for interpolation.
var normalizedTime = playbackTime / iterDuration;
// Check if normalized time needs to be reversed according to PlaybackDirection
bool playbackReversed;
switch (_playbackDirection)
{
case PlaybackDirection.Normal:
playbackReversed = false;
break;
case PlaybackDirection.Reverse:
playbackReversed = true;
break;
case PlaybackDirection.Alternate:
playbackReversed = (_currentIteration % 2 == 0) ? false : true;
break;
case PlaybackDirection.AlternateReverse:
playbackReversed = (_currentIteration % 2 == 0) ? true : false;
break;
default:
throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}");
}
if (playbackReversed)
normalizedTime = 1 - normalizedTime;
// Ease and interpolate
var easedTime = _easeFunc.Ease(normalizedTime);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
PublishNext(_lastInterpValue); PublishNext(_lastInterpValue);
}
else if (playbackTime > iterDuration &
playbackTime <= iterationTime &
iterDelay > 0)
{
// The last iteration's trailing delay should be skipped.
if ((_currentIteration + 1) < _iterationCount)
DoDelay();
else
DoComplete();
}
} }
} }
} }

73
src/Avalonia.Animation/Animator`1.cs → src/Avalonia.Animation/Animators/Animator`1.cs

@ -10,10 +10,10 @@ using Avalonia.Collections;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive; using Avalonia.Reactive;
namespace Avalonia.Animation namespace Avalonia.Animation.Animators
{ {
/// <summary> /// <summary>
/// Base class for KeyFrames objects /// Base class for <see cref="Animator{T}"/> objects
/// </summary> /// </summary>
public abstract class Animator<T> : AvaloniaList<AnimatorKeyFrame>, IAnimator public abstract class Animator<T> : AvaloniaList<AnimatorKeyFrame>, IAnimator
{ {
@ -45,17 +45,10 @@ namespace Avalonia.Animation
return match.Subscribe(subject); return match.Subscribe(subject);
} }
/// <summary> protected T InterpolationHandler(double animationTime, T neutralValue)
/// 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="animationTime">The time parameter, relative to the total animation time</param>
protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double animationTime)
{ {
AnimatorKeyFrame firstKeyframe, lastKeyframe; AnimatorKeyFrame firstKeyframe, lastKeyframe;
int kvCount = _convertedKeyframes.Count; int kvCount = _convertedKeyframes.Count;
if (kvCount > 2) if (kvCount > 2)
{ {
@ -84,38 +77,31 @@ namespace Avalonia.Animation
double t0 = firstKeyframe.Cue.CueValue; double t0 = firstKeyframe.Cue.CueValue;
double t1 = lastKeyframe.Cue.CueValue; double t1 = lastKeyframe.Cue.CueValue;
var intraframeTime = (animationTime - t0) / (t1 - t0);
var firstFrameData = (firstKeyframe.GetTypedValue<T>(), firstKeyframe.isNeutral); double progress = (animationTime - t0) / (t1 - t0);
var lastFrameData = (lastKeyframe.GetTypedValue<T>(), lastKeyframe.isNeutral);
return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData)); T oldValue, newValue;
if (firstKeyframe.isNeutral)
oldValue = neutralValue;
else
oldValue = (T)firstKeyframe.Value;
if (lastKeyframe.isNeutral)
newValue = neutralValue;
else
newValue = (T)lastKeyframe.Value;
return Interpolate(progress, oldValue, newValue);
} }
private int FindClosestBeforeKeyFrame(double time) private int FindClosestBeforeKeyFrame(double time)
{ {
int FindClosestBeforeKeyFrame(int startIndex, int length) for (int i = 0; i < _convertedKeyframes.Count; i++)
{ if (_convertedKeyframes[i].Cue.CueValue > time)
if (length == 0 || length == 1) return i - 1;
{
return startIndex;
}
int middle = startIndex + (length / 2); throw new Exception("Index time is out of keyframe time range.");
if (_convertedKeyframes[middle].Cue.CueValue < time)
{
return FindClosestBeforeKeyFrame(middle, length - middle);
}
else if (_convertedKeyframes[middle].Cue.CueValue > time)
{
return FindClosestBeforeKeyFrame(startIndex, middle - startIndex);
}
else
{
return middle;
}
}
return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count);
} }
/// <summary> /// <summary>
@ -129,18 +115,15 @@ namespace Avalonia.Animation
this, this,
clock ?? control.Clock ?? Clock.GlobalClock, clock ?? control.Clock ?? Clock.GlobalClock,
onComplete, onComplete,
DoInterpolation); InterpolationHandler);
return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation); return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
} }
/// <summary> /// <summary>
/// Interpolates a value given the desired time. /// Interpolates in-between two key values given the desired progress time.
/// </summary> /// </summary>
protected abstract T DoInterpolation(double time, T neutralValue); public abstract T Interpolate(double progress, T oldValue, T newValue);
/// <summary>
/// Verifies, converts and sorts keyframe values according to this class's target type.
/// </summary>
private void VerifyConvertKeyFrames() private void VerifyConvertKeyFrames()
{ {
foreach (AnimatorKeyFrame keyframe in this) foreach (AnimatorKeyFrame keyframe in this)
@ -188,4 +171,4 @@ namespace Avalonia.Animation
} }
} }
} }
} }

21
src/Avalonia.Animation/Animators/BoolAnimator.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.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="bool"/> properties.
/// </summary>
public class BoolAnimator : Animator<bool>
{
/// <inheritdocs/>
public override bool Interpolate(double progress, bool oldValue, bool newValue)
{
if(progress >= 1d)
return newValue;
if(progress >= 0)
return oldValue;
return oldValue;
}
}
}

24
src/Avalonia.Animation/Animators/ByteAnimator.cs

@ -0,0 +1,24 @@
// 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.Animators
{
/// <summary>
/// Animator that handles <see cref="byte"/> properties.
/// </summary>
public class ByteAnimator : Animator<byte>
{
const double maxVal = (double)byte.MaxValue;
/// <inheritdocs/>
public override byte Interpolate(double progress, byte oldValue, byte newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (byte)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

17
src/Avalonia.Animation/Animators/DecimalAnimator.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.Animators
{
/// <summary>
/// Animator that handles <see cref="decimal"/> properties.
/// </summary>
public class DecimalAnimator : Animator<decimal>
{
/// <inheritdocs/>
public override decimal Interpolate(double progress, decimal oldValue, decimal newValue)
{
return ((newValue - oldValue) * (decimal)progress) + oldValue;
}
}
}

17
src/Avalonia.Animation/Animators/DoubleAnimator.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.Animators
{
/// <summary>
/// Animator that handles <see cref="double"/> properties.
/// </summary>
public class DoubleAnimator : Animator<double>
{
/// <inheritdocs/>
public override double Interpolate(double progress, double oldValue, double newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

17
src/Avalonia.Animation/Animators/FloatAnimator.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.Animators
{
/// <summary>
/// Animator that handles <see cref="float"/> properties.
/// </summary>
public class FloatAnimator : Animator<float>
{
/// <inheritdocs/>
public override float Interpolate(double progress, float oldValue, float newValue)
{
return (float)(((newValue - oldValue) * progress) + oldValue);
}
}
}

24
src/Avalonia.Animation/Animators/Int16Animator.cs

@ -0,0 +1,24 @@
// 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.Animators
{
/// <summary>
/// Animator that handles <see cref="Int16"/> properties.
/// </summary>
public class Int16Animator : Animator<Int16>
{
const double maxVal = (double)Int16.MaxValue;
/// <inheritdocs/>
public override Int16 Interpolate(double progress, Int16 oldValue, Int16 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int16)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/Int32Animator.cs

@ -0,0 +1,24 @@
// 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.Animators
{
/// <summary>
/// Animator that handles <see cref="Int32"/> properties.
/// </summary>
public class Int32Animator : Animator<Int32>
{
const double maxVal = (double)Int32.MaxValue;
/// <inheritdocs/>
public override Int32 Interpolate(double progress, Int32 oldValue, Int32 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int32)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/Int64Animator.cs

@ -0,0 +1,24 @@
// 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.Animators
{
/// <summary>
/// Animator that handles <see cref="Int64"/> properties.
/// </summary>
public class Int64Animator : Animator<Int64>
{
const double maxVal = (double)Int64.MaxValue;
/// <inheritdocs/>
public override Int64 Interpolate(double progress, Int64 oldValue, Int64 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int64)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt16Animator.cs

@ -0,0 +1,24 @@
// 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.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt16"/> properties.
/// </summary>
public class UInt16Animator : Animator<UInt16>
{
const double maxVal = (double)UInt16.MaxValue;
/// <inheritdocs/>
public override UInt16 Interpolate(double progress, UInt16 oldValue, UInt16 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt16)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt32Animator.cs

@ -0,0 +1,24 @@
// 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.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt32"/> properties.
/// </summary>
public class UInt32Animator : Animator<UInt32>
{
const double maxVal = (double)UInt32.MaxValue;
/// <inheritdocs/>
public override UInt32 Interpolate(double progress, UInt32 oldValue, UInt32 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt32)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt64Animator.cs

@ -0,0 +1,24 @@
// 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.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt64"/> properties.
/// </summary>
public class UInt64Animator : Animator<UInt64>
{
const double maxVal = (double)UInt64.MaxValue;
/// <inheritdocs/>
public override UInt64 Interpolate(double progress, UInt64 oldValue, UInt64 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt64)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

3
src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils; using Avalonia.Animation.Utils;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Data; using Avalonia.Data;
@ -33,8 +34,6 @@ namespace Avalonia.Animation
this._onComplete = onComplete; this._onComplete = onComplete;
this._clock = clock; this._clock = clock;
} }
public void Dispose() public void Dispose()
{ {
_lastInstance?.Dispose(); _lastInstance?.Dispose();

35
src/Avalonia.Animation/DoubleAnimator.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.
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.isNeutral)
y0 = neutralValue;
else
y0 = firstKF.TargetValue;
if (secondKF.isNeutral)
y1 = neutralValue;
else
y1 = secondKF.TargetValue;
// Do linear parametric interpolation
return y0 + (pair.IntraKFTime) * (y1 - y0);
}
}
}

0
src/Avalonia.Animation/IEasing.cs → src/Avalonia.Animation/Easing/IEasing.cs

176
src/Avalonia.Animation/IterationCount.cs

@ -0,0 +1,176 @@
// 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.ComponentModel;
using System.Globalization;
namespace Avalonia.Animation
{
/// <summary>
/// Defines the valid modes for a <see cref="IterationCount"/>.
/// </summary>
public enum IterationType
{
Many,
Infinite
}
/// <summary>
/// Determines the number of iterations of an animation.
/// Also defines its repeat behavior.
/// </summary>
[TypeConverter(typeof(IterationCountTypeConverter))]
public struct IterationCount : IEquatable<IterationCount>
{
private readonly IterationType _type;
private readonly ulong _value;
/// <summary>
/// Initializes a new instance of the <see cref="IterationCount"/> struct.
/// </summary>
/// <param name="value">The number of iterations of an animation.</param>
public IterationCount(ulong value)
: this(value, IterationType.Many)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IterationCount"/> struct.
/// </summary>
/// <param name="value">The size of the IterationCount.</param>
/// <param name="type">The unit of the IterationCount.</param>
public IterationCount(ulong value, IterationType type)
{
if (type > IterationType.Infinite)
{
throw new ArgumentException("Invalid value", "type");
}
_type = type;
_value = value;
}
/// <summary>
/// Gets an instance of <see cref="IterationCount"/> that indicates that an animation
/// should repeat forever.
/// </summary>
public static IterationCount Infinite => new IterationCount(0, IterationType.Infinite);
/// <summary>
/// Gets the unit of the <see cref="IterationCount"/>.
/// </summary>
public IterationType RepeatType => _type;
/// <summary>
/// Gets a value that indicates whether the <see cref="IterationCount"/> is set to loop.
/// </summary>
public bool IsInfinite => _type == IterationType.Infinite;
/// <summary>
/// Gets the number of repeat iterations.
/// </summary>
public ulong Value => _value;
/// <summary>
/// Compares two IterationCount structures for equality.
/// </summary>
/// <param name="a">The first IterationCount.</param>
/// <param name="b">The second IterationCount.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public static bool operator ==(IterationCount a, IterationCount b)
{
return (a.IsInfinite && b.IsInfinite)
|| (a._value == b._value && a._type == b._type);
}
/// <summary>
/// Compares two IterationCount structures for inequality.
/// </summary>
/// <param name="rc1">The first IterationCount.</param>
/// <param name="rc2">The first IterationCount.</param>
/// <returns>True if the structures are unequal, otherwise false.</returns>
public static bool operator !=(IterationCount rc1, IterationCount rc2)
{
return !(rc1 == rc2);
}
/// <summary>
/// Determines whether the <see cref="IterationCount"/> 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 IterationCount))
{
return false;
}
return this == (IterationCount)o;
}
/// <summary>
/// Compares two IterationCount structures for equality.
/// </summary>
/// <param name="IterationCount">The structure with which to test equality.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public bool Equals(IterationCount IterationCount)
{
return this == IterationCount;
}
/// <summary>
/// Gets a hash code for the IterationCount.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return _value.GetHashCode() ^ _type.GetHashCode();
}
/// <summary>
/// Gets a string representation of the <see cref="IterationCount"/>.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
if (IsInfinite)
{
return "Infinite";
}
string s = _value.ToString();
return s;
}
/// <summary>
/// Parses a string to return a <see cref="IterationCount"/>.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="IterationCount"/>.</returns>
public static IterationCount Parse(string s)
{
s = s.ToUpperInvariant().Trim();
if (s.EndsWith("INFINITE"))
{
return Infinite;
}
else
{
if (s.StartsWith("-"))
throw new InvalidCastException("IterationCount can't be a negative number.");
var value = ulong.Parse(s, CultureInfo.InvariantCulture);
return new IterationCount(value);
}
}
}
}

4
src/Avalonia.Animation/RepeatCountTypeConverter.cs → src/Avalonia.Animation/IterationCountTypeConverter.cs

@ -7,7 +7,7 @@ using System.Globalization;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
public class RepeatCountTypeConverter : TypeConverter public class IterationCountTypeConverter : TypeConverter
{ {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{ {
@ -16,7 +16,7 @@ namespace Avalonia.Animation
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
return RepeatCount.Parse((string)value); return IterationCount.Parse((string)value);
} }
} }
} }

15
src/Avalonia.Animation/KeyFrame.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Animation namespace Avalonia.Animation
{ {
@ -17,7 +18,7 @@ namespace Avalonia.Animation
/// Stores data regarding a specific key /// Stores data regarding a specific key
/// point and value in an animation. /// point and value in an animation.
/// </summary> /// </summary>
public class KeyFrame : AvaloniaList<IAnimationSetter> public class KeyFrame : AvaloniaObject
{ {
private TimeSpan _ktimeSpan; private TimeSpan _ktimeSpan;
private Cue _kCue; private Cue _kCue;
@ -26,13 +27,11 @@ namespace Avalonia.Animation
{ {
} }
public KeyFrame(IEnumerable<IAnimationSetter> items) : base(items) /// <summary>
{ /// Gets the setters of <see cref="KeyFrame"/>.
} /// </summary>
[Content]
public KeyFrame(params IAnimationSetter[] items) : base(items) public AvaloniaList<IAnimationSetter> Setters { get; } = new AvaloniaList<IAnimationSetter>();
{
}
internal KeyFrameTimingMode TimingMode { get; private set; } internal KeyFrameTimingMode TimingMode { get; private set; }

33
src/Avalonia.Animation/KeyFramePair`1.cs

@ -1,33 +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>
/// 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((T TargetValue, bool isNeutral) FirstKeyFrame, (T TargetValue, bool isNeutral) LastKeyFrame) : this()
{
this.FirstKeyFrame = FirstKeyFrame;
this.SecondKeyFrame = LastKeyFrame;
}
/// <summary>
/// First <see cref="KeyFrame"/> object.
/// </summary>
public (T TargetValue, bool isNeutral) FirstKeyFrame { get; }
/// <summary>
/// Second <see cref="KeyFrame"/> object.
/// </summary>
public (T TargetValue, bool isNeutral) SecondKeyFrame { get; }
}
}

33
src/Avalonia.Animation/KeyFrames.cs

@ -0,0 +1,33 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Collections;
namespace Avalonia.Animation
{
/// <summary>
/// A collection of <see cref="KeyFrame"/>s.
/// </summary>
public class KeyFrames : AvaloniaList<KeyFrame>
{
/// <summary>
/// Initializes a new instance of the <see cref="KeyFrames"/> class.
/// </summary>
public KeyFrames()
{
ResetBehavior = ResetBehavior.Remove;
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyFrames"/> class.
/// </summary>
/// <param name="items">The initial items in the collection.</param>
public KeyFrames(IEnumerable<KeyFrame> items)
: base(items)
{
ResetBehavior = ResetBehavior.Remove;
}
}
}

3
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@ -5,4 +5,5 @@ using Avalonia.Metadata;
using System.Reflection; using System.Reflection;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]

199
src/Avalonia.Animation/RepeatCount.cs

@ -1,199 +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.ComponentModel;
using System.Globalization;
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);
}
}
}
}

7
src/Avalonia.Animation/DoubleTransition.cs → src/Avalonia.Animation/Transitions/DoubleTransition.cs

@ -14,9 +14,12 @@ namespace Avalonia.Animation
/// <inheritdocs/> /// <inheritdocs/>
public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue) public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
{ {
var delta = newValue - oldValue;
return progress return progress
.Select(p => Easing.Ease(p) * delta + oldValue); .Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
} }
} }
} }

0
src/Avalonia.Animation/FloatTransition.cs → src/Avalonia.Animation/Transitions/FloatTransition.cs

0
src/Avalonia.Animation/IntegerTransition.cs → src/Avalonia.Animation/Transitions/IntegerTransition.cs

1
src/Avalonia.Controls/Panel.cs

@ -31,7 +31,6 @@ namespace Avalonia.Controls
static Panel() static Panel()
{ {
AffectsRender<Panel>(BackgroundProperty); AffectsRender<Panel>(BackgroundProperty);
ClipToBoundsProperty.OverrideDefaultValue<Panel>(true);
} }
/// <summary> /// <summary>

2
src/Avalonia.Native/PlatformThreadingInterface.cs

@ -68,8 +68,6 @@ namespace Avalonia.Native
lock (l) lock (l)
{ {
cancellation?.Cancel(); cancellation?.Cancel();
cancellation?.Dispose();
cancellation = null;
} }
}); });
try try

4
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -33,7 +33,7 @@
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:3" <Animation Duration="0:0:3"
RepeatCount="Loop" IterationCount="Infinite"
Easing="LinearEasing"> Easing="LinearEasing">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="TranslateTransform.X" <Setter Property="TranslateTransform.X"
@ -49,7 +49,7 @@
<Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:3" <Animation Duration="0:0:3"
RepeatCount="Loop" IterationCount="Infinite"
Easing="LinearEasing"> Easing="LinearEasing">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y"

70
src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs

@ -0,0 +1,70 @@
// Original color interpolation code was written by Romain Guy and Francois Blavoet
// and adopted from LottieSharp Project (https://github.com/ascora/LottieSharp).
using System;
using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that interpolates <see cref="Color"/> through
/// gamma sRGB color space for better visual result.
/// </summary>
public class ColorAnimator : Animator<Color>
{
// Opto-electronic conversion function for the sRGB color space
// Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
private static double OECF_sRGB(double linear)
{
// IEC 61966-2-1:1999
return linear <= 0.0031308d ? linear * 12.92d : (double)(Math.Pow(linear, 1.0d / 2.4d) * 1.055d - 0.055d);
}
// Electro-optical conversion function for the sRGB color space
// Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
private static double EOCF_sRGB(double srgb)
{
// IEC 61966-2-1:1999
return srgb <= 0.04045d ? srgb / 12.92d : (double)Math.Pow((srgb + 0.055d) / 1.055d, 2.4d);
}
public override Color Interpolate(double progress, Color oldValue, Color newValue)
{
// normalize sRGB values.
var oldA = oldValue.A / 255d;
var oldR = oldValue.R / 255d;
var oldG = oldValue.G / 255d;
var oldB = oldValue.B / 255d;
var newA = newValue.A / 255d;
var newR = newValue.R / 255d;
var newG = newValue.G / 255d;
var newB = newValue.B / 255d;
// convert from sRGB to linear
oldR = EOCF_sRGB(oldR);
oldG = EOCF_sRGB(oldG);
oldB = EOCF_sRGB(oldB);
newR = EOCF_sRGB(newR);
newG = EOCF_sRGB(newG);
newB = EOCF_sRGB(newB);
// compute the interpolated color in linear space
var a = oldA + progress * (newA - oldA);
var r = oldR + progress * (newR - oldR);
var g = oldG + progress * (newG - oldG);
var b = oldB + progress * (newB - oldB);
// convert back to sRGB in the [0..255] range
a = a * 255d;
r = OECF_sRGB(r) * 255d;
g = OECF_sRGB(g) * 255d;
b = OECF_sRGB(b) * 255d;
return new Color((byte)Math.Round(a), (byte)Math.Round(r), (byte)Math.Round(g), (byte)Math.Round(b));
}
}
}

27
src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs

@ -0,0 +1,27 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="CornerRadius"/> properties.
/// </summary>
public class CornerRadiusAnimator : Animator<CornerRadius>
{
public override CornerRadius Interpolate(double progress, CornerRadius oldValue, CornerRadius newValue)
{
var deltaTL = newValue.TopLeft - oldValue.TopLeft;
var deltaTR = newValue.TopRight - oldValue.TopRight;
var deltaBR = newValue.BottomRight - oldValue.BottomRight;
var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
var nTL = progress * deltaTL + oldValue.TopLeft;
var nTR = progress * deltaTR + oldValue.TopRight;
var nBR = progress * deltaBR + oldValue.BottomRight;
var nBL = progress * deltaBL + oldValue.BottomLeft;
return new CornerRadius(nTL, nTR, nBR, nBL);
}
}
}

17
src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Point"/> properties.
/// </summary>
public class PointAnimator : Animator<Point>
{
public override Point Interpolate(double progress, Point oldValue, Point newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

23
src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs

@ -0,0 +1,23 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Rect"/> properties.
/// </summary>
public class RectAnimator : Animator<Rect>
{
public override Rect Interpolate(double progress, Rect oldValue, Rect newValue)
{
var deltaPos = newValue.Position - oldValue.Position;
var deltaSize = newValue.Size - oldValue.Size;
var newPos = (deltaPos * progress) + oldValue.Position;
var newSize = (deltaSize * progress) + oldValue.Size;
return new Rect(newPos, newSize);
}
}
}

17
src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Size"/> properties.
/// </summary>
public class SizeAnimator : Animator<Size>
{
public override Size Interpolate(double progress, Size oldValue, Size newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

74
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -0,0 +1,74 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/>.
/// </summary>
public class SolidColorBrushAnimator : Animator<SolidColorBrush>
{
ColorAnimator _colorAnimator;
void InitializeColorAnimator()
{
_colorAnimator = new ColorAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
_colorAnimator.Add(keyframe);
}
_colorAnimator.Property = SolidColorBrush.ColorProperty;
}
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{
foreach (var keyframe in this)
{
if (keyframe.Value as ISolidColorBrush == null)
return Disposable.Empty;
// Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush))
{
keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color;
}
}
// Add SCB if the target prop is empty.
if (control.GetValue(Property) == null)
control.SetValue(Property, new SolidColorBrush(Colors.Transparent));
var targetVal = control.GetValue(Property);
// Continue if target prop is not empty & is a SolidColorBrush derivative.
if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType()))
{
if (_colorAnimator == null)
InitializeColorAnimator();
SolidColorBrush finalTarget;
// If it's ISCB, change it back to SCB.
if (targetVal.GetType() == typeof(ImmutableSolidColorBrush))
{
var col = (ImmutableSolidColorBrush)targetVal;
targetVal = new SolidColorBrush(col.Color);
control.SetValue(Property, targetVal);
}
finalTarget = targetVal as SolidColorBrush;
return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
}
return Disposable.Empty;
}
public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null;
}
}

17
src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Thickness"/> properties.
/// </summary>
public class ThicknessAnimator : Animator<Thickness>
{
public override Thickness Interpolate(double progress, Thickness oldValue, Thickness newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

37
src/Avalonia.Visuals/Animation/TransformAnimator.cs → src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs

@ -2,14 +2,14 @@
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media; using Avalonia.Media;
namespace Avalonia.Animation namespace Avalonia.Animation.Animators
{ {
/// <summary> /// <summary>
/// Animator that handles <see cref="Transform"/> properties. /// Animator that handles <see cref="Transform"/> properties.
/// </summary> /// </summary>
public class TransformAnimator : Animator<double> public class TransformAnimator : Animator<double>
{ {
DoubleAnimator childAnimator; DoubleAnimator _doubleAnimator;
/// <inheritdoc/> /// <inheritdoc/>
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> obsMatch, Action onComplete) public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> obsMatch, Action onComplete)
@ -27,7 +27,7 @@ namespace Avalonia.Animation
// default RenderTransform order. // default RenderTransform order.
normalTransform.Children.Add(new ScaleTransform()); normalTransform.Children.Add(new ScaleTransform());
normalTransform.Children.Add(new SkewTransform()); normalTransform.Children.Add(new SkewTransform());
normalTransform.Children.Add(new RotateTransform()); normalTransform.Children.Add(new RotateTransform());
normalTransform.Children.Add(new TranslateTransform()); normalTransform.Children.Add(new TranslateTransform());
@ -36,15 +36,22 @@ namespace Avalonia.Animation
var renderTransformType = ctrl.RenderTransform.GetType(); var renderTransformType = ctrl.RenderTransform.GetType();
if (childAnimator == null) if (_doubleAnimator == null)
{ {
InitializeChildAnimator(); _doubleAnimator = new DoubleAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
_doubleAnimator.Add(keyframe);
}
_doubleAnimator.Property = Property;
} }
// It's a transform object so let's target that. // It's a transform object so let's target that.
if (renderTransformType == Property.OwnerType) if (renderTransformType == Property.OwnerType)
{ {
return childAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete); return _doubleAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete);
} }
// It's a TransformGroup and try finding the target there. // It's a TransformGroup and try finding the target there.
else if (renderTransformType == typeof(TransformGroup)) else if (renderTransformType == typeof(TransformGroup))
@ -53,7 +60,7 @@ namespace Avalonia.Animation
{ {
if (transform.GetType() == Property.OwnerType) if (transform.GetType() == Property.OwnerType)
{ {
return childAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete); return _doubleAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete);
} }
} }
} }
@ -73,19 +80,7 @@ namespace Avalonia.Animation
return null; return null;
} }
void InitializeChildAnimator() /// <inheritdocs/>
{ public override double Interpolate(double p, double o, double n) => 0;
childAnimator = new DoubleAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
childAnimator.Add(keyframe);
}
childAnimator.Property = Property;
}
/// <inheritdocs/>
protected override double DoInterpolation(double time, double neutralValue) => 0;
} }
} }

17
src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Vector"/> properties.
/// </summary>
public class VectorAnimator : Animator<Vector>
{
public override Vector Interpolate(double progress, Vector oldValue, Vector newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

44
src/Avalonia.Visuals/Animation/CrossFade.cs

@ -21,7 +21,7 @@ namespace Avalonia.Animation
/// Initializes a new instance of the <see cref="CrossFade"/> class. /// Initializes a new instance of the <see cref="CrossFade"/> class.
/// </summary> /// </summary>
public CrossFade() public CrossFade()
:this(TimeSpan.Zero) : this(TimeSpan.Zero)
{ {
} }
@ -33,30 +33,40 @@ namespace Avalonia.Animation
{ {
_fadeOutAnimation = new Animation _fadeOutAnimation = new Animation
{ {
new KeyFrame Children =
( {
new Setter new KeyFrame()
{ {
Property = Visual.OpacityProperty, Setters =
Value = 0d {
new Setter
{
Property = Visual.OpacityProperty,
Value = 0d
}
},
Cue = new Cue(1d)
} }
)
{
Cue = new Cue(1d)
} }
}; };
_fadeInAnimation = new Animation _fadeInAnimation = new Animation
{ {
new KeyFrame Children =
( {
new Setter new KeyFrame()
{ {
Property = Visual.OpacityProperty, Setters =
Value = 0d {
new Setter
{
Property = Visual.OpacityProperty,
Value = 0d
}
},
Cue = new Cue(0d)
} }
)
{
Cue = new Cue(0d)
} }
}; };
_fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration; _fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration;

89
src/Avalonia.Visuals/Animation/PageSlide.cs

@ -74,34 +74,36 @@ namespace Avalonia.Animation
var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty; var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty;
// TODO: Implement relevant transition logic here (or discard this class)
// in favor of XAML based transition for pages
if (from != null) if (from != null)
{ {
var animation = new Animation var animation = new Animation
{ {
new KeyFrame Children =
(
new Setter
{
Property = translateProperty,
Value = 0d
}
)
{ {
Cue = new Cue(0d) new KeyFrame
},
new KeyFrame
(
new Setter
{ {
Property = translateProperty, Setters =
Value = forward ? -distance : distance {
} new Setter
) {
{ Property = translateProperty,
Cue = new Cue(1d) Value = 0d
}
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter
{
Property = translateProperty,
Value = forward ? -distance : distance
}
},
Cue = new Cue(1d)
}
} }
}; };
animation.Duration = Duration; animation.Duration = Duration;
@ -113,29 +115,34 @@ namespace Avalonia.Animation
to.IsVisible = true; to.IsVisible = true;
var animation = new Animation var animation = new Animation
{ {
Children =
{
new KeyFrame new KeyFrame
(
new Setter
{ {
Property = translateProperty, Setters =
Value = forward ? distance : -distance {
} new Setter
) {
{ Property = translateProperty,
Cue = new Cue(0d) Value = forward ? distance : -distance
}, }
new KeyFrame },
( Cue = new Cue(0d)
new Setter },
new KeyFrame
{ {
Property = translateProperty, Setters =
Value = 0d {
new Setter
{
Property = translateProperty,
Value = 0d
}
},
Cue = new Cue(1d)
} }
) }
{
Cue = new Cue(1d)
},
}; };
animation.Duration = Duration; animation.Duration = Duration;
tasks.Add(animation.RunAsync(to)); tasks.Add(animation.RunAsync(to));

36
src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs

@ -0,0 +1,36 @@
// 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.Reactive.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="CornerRadius"/> type.
/// </summary>
public class CornerRadiusTransition : Transition<CornerRadius>
{
/// <inheritdocs/>
public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
var deltaTL = newValue.TopLeft - oldValue.TopLeft;
var deltaTR = newValue.TopRight - oldValue.TopRight;
var deltaBR = newValue.BottomRight - oldValue.BottomRight;
var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
var nTL = f * deltaTL + oldValue.TopLeft;
var nTR = f * deltaTR + oldValue.TopRight;
var nBR = f * deltaBR + oldValue.BottomRight;
var nBL = f * deltaBL + oldValue.BottomLeft;
return new CornerRadius(nTL, nTR, nBR, nBL);
});
}
}
}

7
src/Avalonia.Visuals/Animation/PointTransition.cs → src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs

@ -14,16 +14,11 @@ namespace Avalonia.Animation
/// <inheritdocs/> /// <inheritdocs/>
public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue) public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue)
{ {
var deltaX = newValue.X - oldValue.Y;
var deltaY = newValue.X - oldValue.Y;
return progress return progress
.Select(p => .Select(p =>
{ {
var f = Easing.Ease(p); var f = Easing.Ease(p);
var nX = f * deltaX + oldValue.X; return ((newValue - oldValue) * f) + oldValue;
var nY = f * deltaY + oldValue.Y;
return new Point(nX, nY);
}); });
} }
} }

25
src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs

@ -0,0 +1,25 @@
// 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.Reactive.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Size"/> type.
/// </summary>
public class SizeTransition : Transition<Size>
{
/// <inheritdocs/>
public override IObservable<Size> DoTransition(IObservable<double> progress, Size oldValue, Size newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
}
}
}

13
src/Avalonia.Visuals/Animation/ThicknessTransition.cs → src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs

@ -14,20 +14,11 @@ namespace Avalonia.Animation
/// <inheritdocs/> /// <inheritdocs/>
public override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue) public override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue)
{ {
var deltaL = newValue.Left - oldValue.Left;
var deltaT = newValue.Top - oldValue.Top;
var deltaR = newValue.Right - oldValue.Right;
var deltaB = newValue.Bottom - oldValue.Bottom;
return progress return progress
.Select(p => .Select(p =>
{ {
var f = Easing.Ease(p); var f = Easing.Ease(p);
var nL = f * deltaL + oldValue.Left; return ((newValue - oldValue) * f) + oldValue;
var nT = f * deltaT + oldValue.Right;
var nR = f * deltaR + oldValue.Top;
var nB = f * deltaB + oldValue.Bottom;
return new Thickness(nL, nT, nR, nB);
}); });
} }
} }

25
src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs

@ -0,0 +1,25 @@
// 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.Reactive.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Vector"/> type.
/// </summary>
public class VectorTransition : Transition<Vector>
{
/// <inheritdocs/>
public override IObservable<Vector> DoTransition(IObservable<double> progress, Vector oldValue, Vector newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
}
}
}

6
src/Avalonia.Visuals/CornerRadius.cs

@ -3,12 +3,18 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
{ {
public struct CornerRadius public struct CornerRadius
{ {
static CornerRadius()
{
Animation.Animation.RegisterAnimator<CornerRadiusAnimator>(prop => typeof(CornerRadius).IsAssignableFrom(prop.PropertyType));
}
public CornerRadius(double uniformRadius) public CornerRadius(double uniformRadius)
{ {
TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius; TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius;

3
src/Avalonia.Visuals/Media/Brush.cs

@ -3,6 +3,7 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Animation;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -10,7 +11,7 @@ namespace Avalonia.Media
/// Describes how an area is painted. /// Describes how an area is painted.
/// </summary> /// </summary>
[TypeConverter(typeof(BrushConverter))] [TypeConverter(typeof(BrushConverter))]
public abstract class Brush : AvaloniaObject, IMutableBrush public abstract class Brush : Animatable, IMutableBrush
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Opacity"/> property. /// Defines the <see cref="Opacity"/> property.

7
src/Avalonia.Visuals/Media/Color.cs

@ -3,6 +3,8 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -11,6 +13,11 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public readonly struct Color public readonly struct Color
{ {
static Color()
{
Animation.Animation.RegisterAnimator<ColorAnimator>(prop => typeof(Color).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// Gets or sets the Alpha component of the color. /// Gets or sets the Alpha component of the color.
/// </summary> /// </summary>

3
src/Avalonia.Visuals/Media/SolidColorBrush.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
namespace Avalonia.Media namespace Avalonia.Media
@ -18,6 +20,7 @@ namespace Avalonia.Media
static SolidColorBrush() static SolidColorBrush()
{ {
Animation.Animation.RegisterAnimator<SolidColorBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
AffectsRender<SolidColorBrush>(ColorProperty); AffectsRender<SolidColorBrush>(ColorProperty);
} }

1
src/Avalonia.Visuals/Media/Transform.cs

@ -3,6 +3,7 @@
using System; using System;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Media namespace Avalonia.Media

14
src/Avalonia.Visuals/Point.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Globalization; using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
@ -11,6 +12,11 @@ namespace Avalonia
/// </summary> /// </summary>
public readonly struct Point public readonly struct Point
{ {
static Point()
{
Animation.Animation.RegisterAnimator<PointAnimator>(prop => typeof(Point).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// The X position. /// The X position.
/// </summary> /// </summary>
@ -133,7 +139,7 @@ namespace Avalonia
/// <param name="p">Point to multiply</param> /// <param name="p">Point to multiply</param>
/// <param name="k">Factor</param> /// <param name="k">Factor</param>
/// <returns>Points having its coordinates multiplied</returns> /// <returns>Points having its coordinates multiplied</returns>
public static Point operator *(Point p, double k) => new Point(p.X*k, p.Y*k); public static Point operator *(Point p, double k) => new Point(p.X * k, p.Y * k);
/// <summary> /// <summary>
/// Multiplies a point by a factor coordinate-wise /// Multiplies a point by a factor coordinate-wise
@ -141,7 +147,7 @@ namespace Avalonia
/// <param name="p">Point to multiply</param> /// <param name="p">Point to multiply</param>
/// <param name="k">Factor</param> /// <param name="k">Factor</param>
/// <returns>Points having its coordinates multiplied</returns> /// <returns>Points having its coordinates multiplied</returns>
public static Point operator *(double k, Point p) => new Point(p.X*k, p.Y*k); public static Point operator *(double k, Point p) => new Point(p.X * k, p.Y * k);
/// <summary> /// <summary>
/// Divides a point by a factor coordinate-wise /// Divides a point by a factor coordinate-wise
@ -149,8 +155,8 @@ namespace Avalonia
/// <param name="p">Point to divide by</param> /// <param name="p">Point to divide by</param>
/// <param name="k">Factor</param> /// <param name="k">Factor</param>
/// <returns>Points having its coordinates divided</returns> /// <returns>Points having its coordinates divided</returns>
public static Point operator /(Point p, double k) => new Point(p.X/k, p.Y/k); public static Point operator /(Point p, double k) => new Point(p.X / k, p.Y / k);
/// <summary> /// <summary>
/// Applies a matrix to a point. /// Applies a matrix to a point.
/// </summary> /// </summary>

6
src/Avalonia.Visuals/Rect.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
@ -12,6 +13,11 @@ namespace Avalonia
/// </summary> /// </summary>
public readonly struct Rect public readonly struct Rect
{ {
static Rect()
{
Animation.Animation.RegisterAnimator<RectAnimator>(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// An empty rectangle. /// An empty rectangle.
/// </summary> /// </summary>

6
src/Avalonia.Visuals/Size.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
@ -12,6 +13,11 @@ namespace Avalonia
/// </summary> /// </summary>
public readonly struct Size public readonly struct Size
{ {
static Size()
{
Animation.Animation.RegisterAnimator<SizeAnimator>(prop => typeof(Size).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// A size representing infinity. /// A size representing infinity.
/// </summary> /// </summary>

39
src/Avalonia.Visuals/Thickness.cs

@ -3,6 +3,8 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
@ -12,6 +14,11 @@ namespace Avalonia
/// </summary> /// </summary>
public readonly struct Thickness public readonly struct Thickness
{ {
static Thickness()
{
Animation.Animation.RegisterAnimator<ThicknessAnimator>(prop => typeof(Thickness).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// The thickness on the left. /// The thickness on the left.
/// </summary> /// </summary>
@ -134,6 +141,36 @@ namespace Avalonia
a.Bottom + b.Bottom); a.Bottom + b.Bottom);
} }
/// <summary>
/// Subtracts two Thicknesses.
/// </summary>
/// <param name="a">The first thickness.</param>
/// <param name="b">The second thickness.</param>
/// <returns>The equality.</returns>
public static Thickness operator -(Thickness a, Thickness b)
{
return new Thickness(
a.Left - b.Left,
a.Top - b.Top,
a.Right - b.Right,
a.Bottom - b.Bottom);
}
/// <summary>
/// Multiplies a Thickness to a scalar.
/// </summary>
/// <param name="a">The thickness.</param>
/// <param name="b">The scalar.</param>
/// <returns>The equality.</returns>
public static Thickness operator *(Thickness a, double b)
{
return new Thickness(
a.Left * b,
a.Top * b,
a.Right * b,
a.Bottom * b);
}
/// <summary> /// <summary>
/// Adds a Thickness to a Size. /// Adds a Thickness to a Size.
/// </summary> /// </summary>
@ -169,7 +206,7 @@ namespace Avalonia
{ {
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness")) using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
{ {
if(tokenizer.TryReadDouble(out var a)) if (tokenizer.TryReadDouble(out var a))
{ {
if (tokenizer.TryReadDouble(out var b)) if (tokenizer.TryReadDouble(out var b))
{ {

10
src/Avalonia.Visuals/Vector.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Avalonia.Animation.Animators;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace Avalonia namespace Avalonia
@ -12,6 +13,11 @@ namespace Avalonia
/// </summary> /// </summary>
public readonly struct Vector public readonly struct Vector
{ {
static Vector()
{
Animation.Animation.RegisterAnimator<VectorAnimator>(prop => typeof(Vector).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// The X vector. /// The X vector.
/// </summary> /// </summary>
@ -60,7 +66,7 @@ namespace Avalonia
/// <returns>The dot product</returns> /// <returns>The dot product</returns>
public static double operator *(Vector a, Vector b) public static double operator *(Vector a, Vector b)
{ {
return a.X*b.X + a.Y*b.Y; return a.X * b.X + a.Y * b.Y;
} }
/// <summary> /// <summary>
@ -88,7 +94,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Length of the vector /// Length of the vector
/// </summary> /// </summary>
public double Length => Math.Sqrt(X*X + Y*Y); public double Length => Math.Sqrt(X * X + Y * Y);
/// <summary> /// <summary>
/// Negates a vector. /// Negates a vector.

2
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -137,7 +137,7 @@ namespace Avalonia.Skia
{ {
var colorType = PixelFormatHelper.ResolveColorType(format); var colorType = PixelFormatHelper.ResolveColorType(format);
return new SKImageInfo(width, height, colorType, SKAlphaType.Premul); return new SKImageInfo(Math.Max(width, 1), Math.Max(height, 1), colorType, SKAlphaType.Premul);
} }
/// <summary> /// <summary>

77
tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs

@ -0,0 +1,77 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.Data;
using Xunit;
namespace Avalonia.Animation.UnitTests
{
public class AnimationIterationTests
{
[Fact]
public void Check_Initial_Inter_and_Trailing_Delay_Values()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(3),
Delay = TimeSpan.FromSeconds(3),
DelayBetweenIterations = TimeSpan.FromSeconds(3),
IterationCount = new IterationCount(2),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 100d
};
var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock);
clock.Step(TimeSpan.Zero);
// Initial Delay.
clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(border.Width, 0d);
clock.Step(TimeSpan.FromSeconds(6));
// First Inter-Iteration delay.
clock.Step(TimeSpan.FromSeconds(8));
Assert.Equal(border.Width, 200d);
// Trailing Delay should be non-existent.
clock.Step(TimeSpan.FromSeconds(14));
Assert.True(animationRun.Status == TaskStatus.RanToCompletion);
Assert.Equal(border.Width, 100d);
}
}
}

25
tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

10
tests/Avalonia.Animation.UnitTests/Properties/AssemblyInfo.cs

@ -0,0 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection;
using Xunit;
[assembly: AssemblyTitle("Avalonia.Animation.UnitTests")]
// Don't run tests in parallel.
[assembly: CollectionBehavior(DisableTestParallelization = true)]

28
tests/Avalonia.Animation.UnitTests/TestClock.cs

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Animation.UnitTests
{
internal class TestClock : IClock, IDisposable
{
private IObserver<TimeSpan> _observer;
public PlayState PlayState { get; set; } = PlayState.Run;
public void Dispose()
{
_observer?.OnCompleted();
}
public void Step(TimeSpan time)
{
_observer?.OnNext(time);
}
public IDisposable Subscribe(IObserver<TimeSpan> observer)
{
_observer = observer;
return this;
}
}
}
Loading…
Cancel
Save