420 changed files with 6893 additions and 8189 deletions
@ -1,7 +1,3 @@ |
|||
[submodule "src/Avalonia.HtmlRenderer/external"] |
|||
path = src/Avalonia.HtmlRenderer/external |
|||
url = https://github.com/AvaloniaUI/HTML-Renderer.git |
|||
branch = perspex-pcl |
|||
[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"] |
|||
path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github |
|||
url = https://github.com/AvaloniaUI/Portable.Xaml.git |
|||
|
|||
@ -0,0 +1,8 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" /> |
|||
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" /> |
|||
<PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" /> |
|||
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,5 +1,5 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="reactiveui" Version="8.0.0-alpha0073" /> |
|||
<PackageReference Include="reactiveui" Version="8.0.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,5 @@ |
|||
<Project> |
|||
<ItemGroup> |
|||
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.0" PrivateAssets="All" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,2 +1,123 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui"> |
|||
<UserControl |
|||
xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<UserControl.Styles> |
|||
<Styles> |
|||
<Styles.Resources> |
|||
<Template x:Key="Acorn"> |
|||
<Path Fill="White" Stretch="Uniform" |
|||
Data="F1 M 16.6309,18.6563C 17.1309, |
|||
8.15625 29.8809,14.1563 29.8809, |
|||
14.1563C 30.8809,11.1563 34.1308, |
|||
11.4063 34.1308,11.4063C 33.5,12 |
|||
34.6309,13.1563 34.6309,13.1563C |
|||
32.1309,13.1562 31.1309,14.9062 |
|||
31.1309,14.9062C 41.1309,23.9062 |
|||
32.6309,27.9063 32.6309,27.9062C |
|||
24.6309,24.9063 21.1309,22.1562 |
|||
16.6309,18.6563 Z M 16.6309,19.9063C |
|||
21.6309,24.1563 25.1309,26.1562 |
|||
31.6309,28.6562C 31.6309,28.6562 |
|||
26.3809,39.1562 18.3809,36.1563C |
|||
18.3809,36.1563 18,38 16.3809,36.9063C |
|||
15,36 16.3809,34.9063 16.3809,34.9063C |
|||
16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z"/> |
|||
</Template> |
|||
<Template x:Key="Heart"> |
|||
<Path Fill="Red" Stretch="Uniform" Data=" |
|||
M 272.70141,238.71731 |
|||
C 206.46141,238.71731 152.70146,292.4773 152.70146,358.71731 |
|||
C 152.70146,493.47282 288.63461,528.80461 381.26391,662.02535 |
|||
C 468.83815,529.62199 609.82641,489.17075 609.82641,358.71731 |
|||
C 609.82641,292.47731 556.06651,238.7173 489.82641,238.71731 |
|||
C 441.77851,238.71731 400.42481,267.08774 381.26391,307.90481 |
|||
C 362.10311,267.08773 320.74941,238.7173 272.70141,238.71731 z "/> |
|||
</Template> |
|||
</Styles.Resources> |
|||
<Style Selector="Border.Test"> |
|||
<Setter Property="Margin" Value="15"/> |
|||
<Setter Property="Width" Value="100"/> |
|||
<Setter Property="Height" Value="100"/> |
|||
<Setter Property="Child" Value="{StaticResource Acorn}"/> |
|||
</Style> |
|||
<Style Selector="Border.Rect1:pointerover"> |
|||
<Style.Animations> |
|||
<Animation Duration="0:0:2.5" |
|||
RepeatCount="4" |
|||
FillMode="None" |
|||
PlaybackDirection="AlternateReverse" |
|||
Easing="SineEaseInOut"> |
|||
<KeyFrame Cue="20%"> |
|||
<Setter Property="RotateTransform.Angle" Value="45"/> |
|||
</KeyFrame> |
|||
<KeyFrame Cue="50%"> |
|||
<Setter Property="ScaleTransform.ScaleX" Value="1.5"/> |
|||
</KeyFrame> |
|||
<KeyFrame Cue="80%"> |
|||
<Setter Property="RotateTransform.Angle" Value="120"/> |
|||
</KeyFrame> |
|||
</Animation> |
|||
</Style.Animations> |
|||
</Style> |
|||
<Style Selector="Border.Rect2:pointerover"> |
|||
<Style.Animations> |
|||
<Animation Duration="0:0:0.5" Easing="SineEaseInOut"> |
|||
<KeyFrame Cue="50%"> |
|||
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/> |
|||
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/> |
|||
</KeyFrame> |
|||
</Animation> |
|||
</Style.Animations> |
|||
</Style> |
|||
<Style Selector="Border.Rect3"> |
|||
<Setter Property="Child" Value="{StaticResource Heart}"/> |
|||
<Style.Animations> |
|||
<Animation Duration="0:0:0.5" |
|||
Easing="QuadraticEaseInOut" |
|||
RepeatCount="Loop"> |
|||
<KeyFrame Cue="50%"> |
|||
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/> |
|||
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/> |
|||
</KeyFrame> |
|||
</Animation> |
|||
</Style.Animations> |
|||
</Style> |
|||
<Style Selector="Border.Rect4:pointerover"> |
|||
<Style.Animations> |
|||
<Animation Duration="0:0:3" Easing="BounceEaseInOut"> |
|||
<KeyFrame Cue="48%"> |
|||
<Setter Property="TranslateTransform.Y" Value="-100"/> |
|||
</KeyFrame> |
|||
</Animation> |
|||
</Style.Animations> |
|||
</Style> |
|||
<Style Selector="Border.Rect5:pointerover"> |
|||
<Style.Animations> |
|||
<Animation Duration="0:0:3" Easing="CircularEaseInOut"> |
|||
<KeyFrame Cue="25%"> |
|||
<Setter Property="SkewTransform.AngleX" Value="-20"/> |
|||
</KeyFrame> |
|||
<KeyFrame Cue="75%"> |
|||
<Setter Property="SkewTransform.AngleX" Value="20"/> |
|||
</KeyFrame> |
|||
</Animation> |
|||
</Style.Animations> |
|||
</Style> |
|||
</Styles> |
|||
</UserControl.Styles> |
|||
<Grid> |
|||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False"> |
|||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> |
|||
<TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock> |
|||
<Button Content="{Binding PlayStateText}" Command="{Binding ToggleGlobalPlayState}"/> |
|||
</StackPanel> |
|||
<WrapPanel ClipToBounds="False"> |
|||
<Border Classes="Test Rect1" Background="DarkRed"/> |
|||
<Border Classes="Test Rect2" Background="Magenta"/> |
|||
<Border Classes="Test Rect3"/> |
|||
<Border Classes="Test Rect4" Background="Navy"/> |
|||
<Border Classes="Test Rect5" Background="SeaGreen"/> |
|||
</WrapPanel> |
|||
</StackPanel> |
|||
</Grid> |
|||
</UserControl> |
|||
@ -1,24 +0,0 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Logging.Serilog; |
|||
using Avalonia.Platform; |
|||
using Serilog; |
|||
|
|||
namespace RenderTest |
|||
{ |
|||
internal class Program |
|||
{ |
|||
static void Main(string[] args) |
|||
{ |
|||
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args
|
|||
// again.
|
|||
AppBuilder.Configure<App>() |
|||
.UsePlatformDetect() |
|||
.UseReactiveUI() |
|||
.LogToDebug() |
|||
.Start<MainWindow>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using ReactiveUI; |
|||
using Avalonia.Animation; |
|||
|
|||
namespace RenderTest.ViewModels |
|||
{ |
|||
public class AnimationsPageViewModel : ReactiveObject |
|||
{ |
|||
private string _playStateText = "Pause all animations"; |
|||
|
|||
public AnimationsPageViewModel() |
|||
{ |
|||
ToggleGlobalPlayState = ReactiveCommand.Create(()=>TogglePlayState()); |
|||
} |
|||
|
|||
void TogglePlayState() |
|||
{ |
|||
switch (Timing.GetGlobalPlayState()) |
|||
{ |
|||
case PlayState.Run: |
|||
PlayStateText = "Resume all animations"; |
|||
Timing.SetGlobalPlayState(PlayState.Pause); |
|||
break; |
|||
|
|||
case PlayState.Pause: |
|||
PlayStateText = "Pause all animations"; |
|||
Timing.SetGlobalPlayState(PlayState.Run); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
public string PlayStateText |
|||
{ |
|||
get { return _playStateText; } |
|||
set { this.RaiseAndSetIfChanged(ref _playStateText, value); } |
|||
} |
|||
|
|||
public ReactiveCommand ToggleGlobalPlayState { get; } |
|||
} |
|||
} |
|||
@ -1,134 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Data; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Utilities for creating animations.
|
|||
/// </summary>
|
|||
public static class Animate |
|||
{ |
|||
/// <summary>
|
|||
/// The number of frames per second.
|
|||
/// </summary>
|
|||
public const int FramesPerSecond = 60; |
|||
|
|||
/// <summary>
|
|||
/// The time span of each frame.
|
|||
/// </summary>
|
|||
private static readonly TimeSpan Tick = TimeSpan.FromSeconds(1.0 / FramesPerSecond); |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="Animate"/> class.
|
|||
/// </summary>
|
|||
static Animate() |
|||
{ |
|||
Stopwatch = new Stopwatch(); |
|||
Stopwatch.Start(); |
|||
Timer = Observable.Interval(Tick, AvaloniaScheduler.Instance) |
|||
.Select(_ => Stopwatch.Elapsed) |
|||
.Publish() |
|||
.RefCount(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The stopwatch used to track time.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The stopwatch used to track time.
|
|||
/// </value>
|
|||
public static Stopwatch Stopwatch |
|||
{ |
|||
get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the animation timer.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The animation timer ticks <see cref="FramesPerSecond"/> times per second. The
|
|||
/// parameter passed to a subsciber is the time span since the animation system was
|
|||
/// initialized.
|
|||
/// </remarks>
|
|||
/// <value>
|
|||
/// The animation timer.
|
|||
/// </value>
|
|||
public static IObservable<TimeSpan> Timer |
|||
{ |
|||
get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a timer that fires every frame for the specified duration.
|
|||
/// </summary>
|
|||
/// <param name="duration">The duration of the animation.</param>
|
|||
/// <returns>
|
|||
/// An observable that notifies the subscriber of the progress along the animation.
|
|||
/// </returns>
|
|||
/// <remarks>
|
|||
/// The parameter passed to the subscriber is the progress along the animation, with
|
|||
/// 0 being the start and 1 being the end. The observable is guaranteed to fire 0
|
|||
/// immediately on subscribe and 1 at the end of the duration.
|
|||
/// </remarks>
|
|||
public static IObservable<double> GetTimer(TimeSpan duration) |
|||
{ |
|||
var startTime = Stopwatch.Elapsed.Ticks; |
|||
var endTime = startTime + duration.Ticks; |
|||
return Timer |
|||
.TakeWhile(x => x.Ticks < endTime) |
|||
.Select(x => (x.Ticks - startTime) / (double)duration.Ticks) |
|||
.StartWith(0.0) |
|||
.Concat(Observable.Return(1.0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Animates a <see cref="AvaloniaProperty"/>.
|
|||
/// </summary>
|
|||
/// <param name="target">The target object.</param>
|
|||
/// <param name="property">The target property.</param>
|
|||
/// <param name="start">The value of the property at the start of the animation.</param>
|
|||
/// <param name="finish">The value of the property at the end of the animation.</param>
|
|||
/// <param name="easing">The easing function to use.</param>
|
|||
/// <param name="duration">The duration of the animation.</param>
|
|||
/// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns>
|
|||
public static Animation Property( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty property, |
|||
object start, |
|||
object finish, |
|||
IEasing easing, |
|||
TimeSpan duration) |
|||
{ |
|||
var o = GetTimer(duration).Select(progress => easing.Ease(progress, start, finish)); |
|||
return new Animation(o, target.Bind(property, o, BindingPriority.Animation)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Animates a <see cref="AvaloniaProperty"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The property type.</typeparam>
|
|||
/// <param name="target">The target object.</param>
|
|||
/// <param name="property">The target property.</param>
|
|||
/// <param name="start">The value of the property at the start of the animation.</param>
|
|||
/// <param name="finish">The value of the property at the end of the animation.</param>
|
|||
/// <param name="easing">The easing function to use.</param>
|
|||
/// <param name="duration">The duration of the animation.</param>
|
|||
/// <returns>An <see cref="Animation"/> that can be used to track or stop the animation.</returns>
|
|||
public static Animation<T> Property<T>( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty<T> property, |
|||
T start, |
|||
T finish, |
|||
IEasing<T> easing, |
|||
TimeSpan duration) |
|||
{ |
|||
var o = GetTimer(duration).Select(progress => easing.Ease(progress, start, finish)); |
|||
return new Animation<T>(o, target.Bind(property, o, BindingPriority.Animation)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,45 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines animation extension methods.
|
|||
/// </summary>
|
|||
public static class AnimationExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Returns a new <see cref="PropertyTransition"/> for the specified
|
|||
/// <see cref="AvaloniaProperty"/> using linear easing.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the <see cref="AvaloniaProperty"/>.</typeparam>
|
|||
/// <param name="property">The property to animate.</param>
|
|||
/// <param name="milliseconds">The animation duration in milliseconds.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="PropertyTransition"/> that can be added to the
|
|||
/// <see cref="Animatable.PropertyTransitions"/> collection.
|
|||
/// </returns>
|
|||
public static PropertyTransition Transition<T>(this AvaloniaProperty<T> property, int milliseconds) |
|||
{ |
|||
return Transition(property, TimeSpan.FromMilliseconds(milliseconds)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="PropertyTransition"/> for the specified
|
|||
/// <see cref="AvaloniaProperty"/> using linear easing.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the <see cref="AvaloniaProperty"/>.</typeparam>
|
|||
/// <param name="property">The property to animate.</param>
|
|||
/// <param name="duration">The animation duration.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="PropertyTransition"/> that can be added to the
|
|||
/// <see cref="Animatable.PropertyTransitions"/> collection.
|
|||
/// </returns>
|
|||
public static PropertyTransition Transition<T>(this AvaloniaProperty<T> property, TimeSpan duration) |
|||
{ |
|||
return new PropertyTransition(property, duration, LinearEasing.For<T>()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,56 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Tracks the progress of an animation.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the value being animated./</typeparam>
|
|||
public class Animation<T> : IObservable<T>, IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The animation being tracked.
|
|||
/// </summary>
|
|||
private readonly IObservable<T> _inner; |
|||
|
|||
/// <summary>
|
|||
/// The disposable used to cancel the animation.
|
|||
/// </summary>
|
|||
private readonly IDisposable _subscription; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Animation{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="inner">The animation observable being tracked.</param>
|
|||
/// <param name="subscription">A disposable used to cancel the animation.</param>
|
|||
public Animation(IObservable<T> inner, IDisposable subscription) |
|||
{ |
|||
_inner = inner; |
|||
_subscription = subscription; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Cancels the animation.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
_subscription.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notifies the provider that an observer is to receive notifications.
|
|||
/// </summary>
|
|||
/// <param name="observer">The observer.</param>
|
|||
/// <returns>
|
|||
/// A reference to an interface that allows observers to stop receiving notifications
|
|||
/// before the provider has finished sending them.
|
|||
/// </returns>
|
|||
public IDisposable Subscribe(IObserver<T> observer) |
|||
{ |
|||
return _inner.Subscribe(observer); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.ComponentModel; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a KeyFrame that is used for
|
|||
/// <see cref="Animators"/> objects.
|
|||
/// </summary>
|
|||
public class AnimatorKeyFrame |
|||
{ |
|||
public Type Handler; |
|||
public Cue Cue; |
|||
public TimeSpan KeyTime; |
|||
internal bool timeSpanSet, cueSet; |
|||
public AvaloniaProperty Property; |
|||
public object Value; |
|||
} |
|||
} |
|||
@ -0,0 +1,256 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Provides statefulness for an iteration of a keyframe animation.
|
|||
/// </summary>
|
|||
internal class AnimatorStateMachine<T> : IObservable<object>, IDisposable |
|||
{ |
|||
object _lastInterpValue; |
|||
object _firstKFValue; |
|||
|
|||
private ulong _delayTotalFrameCount; |
|||
private ulong _durationTotalFrameCount; |
|||
private ulong _delayFrameCount; |
|||
private ulong _durationFrameCount; |
|||
private ulong _repeatCount; |
|||
private ulong _currentIteration; |
|||
|
|||
private bool _isLooping; |
|||
private bool _isRepeating; |
|||
private bool _isReversed; |
|||
private bool _checkLoopAndRepeat; |
|||
private bool _gotFirstKFValue; |
|||
|
|||
private FillMode _fillMode; |
|||
private PlaybackDirection _animationDirection; |
|||
private KeyFramesStates _currentState; |
|||
private KeyFramesStates _savedState; |
|||
private Animator<T> _parent; |
|||
private Animation _targetAnimation; |
|||
private Animatable _targetControl; |
|||
private T _neutralValue; |
|||
internal bool _unsubscribe = false; |
|||
private IObserver<object> _targetObserver; |
|||
|
|||
[Flags] |
|||
private enum KeyFramesStates |
|||
{ |
|||
Initialize, |
|||
DoDelay, |
|||
DoRun, |
|||
RunForwards, |
|||
RunBackwards, |
|||
RunApplyValue, |
|||
RunComplete, |
|||
Pause, |
|||
Stop, |
|||
Disposed |
|||
} |
|||
|
|||
public void Initialize(Animation animation, Animatable control, Animator<T> keyframes) |
|||
{ |
|||
_parent = keyframes; |
|||
_targetAnimation = animation; |
|||
_targetControl = control; |
|||
_neutralValue = (T)_targetControl.GetValue(_parent.Property); |
|||
|
|||
_delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks); |
|||
_durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks); |
|||
|
|||
switch (animation.RepeatCount.RepeatType) |
|||
{ |
|||
case RepeatType.Loop: |
|||
_isLooping = true; |
|||
_checkLoopAndRepeat = true; |
|||
break; |
|||
case RepeatType.Repeat: |
|||
_isRepeating = true; |
|||
_checkLoopAndRepeat = true; |
|||
_repeatCount = animation.RepeatCount.Value; |
|||
break; |
|||
} |
|||
|
|||
_isReversed = (animation.PlaybackDirection & PlaybackDirection.Reverse) != 0; |
|||
_animationDirection = _targetAnimation.PlaybackDirection; |
|||
_fillMode = _targetAnimation.FillMode; |
|||
|
|||
if (_durationTotalFrameCount > 0) |
|||
_currentState = KeyFramesStates.DoDelay; |
|||
else |
|||
_currentState = KeyFramesStates.DoRun; |
|||
} |
|||
|
|||
public void Step(PlayState _playState, Func<double, T, T> Interpolator) |
|||
{ |
|||
try |
|||
{ |
|||
InternalStep(_playState, Interpolator); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
_targetObserver?.OnError(e); |
|||
} |
|||
} |
|||
|
|||
private void InternalStep(PlayState _playState, Func<double, T, T> Interpolator) |
|||
{ |
|||
if (!_gotFirstKFValue) |
|||
{ |
|||
_firstKFValue = _parent.First().Value; |
|||
_gotFirstKFValue = true; |
|||
} |
|||
|
|||
if (_currentState == KeyFramesStates.Disposed) |
|||
throw new InvalidProgramException("This KeyFrames Animation is already disposed."); |
|||
|
|||
if (_playState == PlayState.Stop) |
|||
_currentState = KeyFramesStates.Stop; |
|||
|
|||
// Save state and pause the machine
|
|||
if (_playState == PlayState.Pause && _currentState != KeyFramesStates.Pause) |
|||
{ |
|||
_savedState = _currentState; |
|||
_currentState = KeyFramesStates.Pause; |
|||
} |
|||
|
|||
// Resume the previous state
|
|||
if (_playState != PlayState.Pause && _currentState == KeyFramesStates.Pause) |
|||
_currentState = _savedState; |
|||
|
|||
double _tempDuration = 0d, _easedTime; |
|||
|
|||
checkstate: |
|||
switch (_currentState) |
|||
{ |
|||
case KeyFramesStates.DoDelay: |
|||
|
|||
if (_fillMode == FillMode.Backward |
|||
|| _fillMode == FillMode.Both) |
|||
{ |
|||
if (_currentIteration == 0) |
|||
{ |
|||
_targetObserver.OnNext(_firstKFValue); |
|||
} |
|||
else |
|||
{ |
|||
_targetObserver.OnNext(_lastInterpValue); |
|||
} |
|||
} |
|||
|
|||
if (_delayFrameCount > _delayTotalFrameCount) |
|||
{ |
|||
_currentState = KeyFramesStates.DoRun; |
|||
goto checkstate; |
|||
} |
|||
_delayFrameCount++; |
|||
break; |
|||
|
|||
case KeyFramesStates.DoRun: |
|||
|
|||
if (_isReversed) |
|||
_currentState = KeyFramesStates.RunBackwards; |
|||
else |
|||
_currentState = KeyFramesStates.RunForwards; |
|||
|
|||
goto checkstate; |
|||
|
|||
case KeyFramesStates.RunForwards: |
|||
|
|||
if (_durationFrameCount > _durationTotalFrameCount) |
|||
{ |
|||
_currentState = KeyFramesStates.RunComplete; |
|||
goto checkstate; |
|||
} |
|||
|
|||
_tempDuration = (double)_durationFrameCount / _durationTotalFrameCount; |
|||
_currentState = KeyFramesStates.RunApplyValue; |
|||
|
|||
goto checkstate; |
|||
|
|||
case KeyFramesStates.RunBackwards: |
|||
|
|||
if (_durationFrameCount > _durationTotalFrameCount) |
|||
{ |
|||
_currentState = KeyFramesStates.RunComplete; |
|||
goto checkstate; |
|||
} |
|||
|
|||
_tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount; |
|||
_currentState = KeyFramesStates.RunApplyValue; |
|||
|
|||
goto checkstate; |
|||
|
|||
case KeyFramesStates.RunApplyValue: |
|||
|
|||
_easedTime = _targetAnimation.Easing.Ease(_tempDuration); |
|||
|
|||
_durationFrameCount++; |
|||
_lastInterpValue = Interpolator(_easedTime, _neutralValue); |
|||
_targetObserver.OnNext(_lastInterpValue); |
|||
_currentState = KeyFramesStates.DoRun; |
|||
|
|||
break; |
|||
|
|||
case KeyFramesStates.RunComplete: |
|||
|
|||
if (_checkLoopAndRepeat) |
|||
{ |
|||
_delayFrameCount = 0; |
|||
_durationFrameCount = 0; |
|||
|
|||
if (_isLooping) |
|||
{ |
|||
_currentState = KeyFramesStates.DoRun; |
|||
} |
|||
else if (_isRepeating) |
|||
{ |
|||
if (_currentIteration >= _repeatCount) |
|||
{ |
|||
_currentState = KeyFramesStates.Stop; |
|||
} |
|||
else |
|||
{ |
|||
_currentState = KeyFramesStates.DoRun; |
|||
} |
|||
_currentIteration++; |
|||
} |
|||
|
|||
if (_animationDirection == PlaybackDirection.Alternate |
|||
|| _animationDirection == PlaybackDirection.AlternateReverse) |
|||
_isReversed = !_isReversed; |
|||
|
|||
break; |
|||
} |
|||
|
|||
_currentState = KeyFramesStates.Stop; |
|||
goto checkstate; |
|||
|
|||
case KeyFramesStates.Stop: |
|||
|
|||
if (_fillMode == FillMode.Forward |
|||
|| _fillMode == FillMode.Both) |
|||
{ |
|||
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); |
|||
} |
|||
_targetObserver.OnCompleted(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
public IDisposable Subscribe(IObserver<object> observer) |
|||
{ |
|||
_targetObserver = observer; |
|||
return this; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_unsubscribe = true; |
|||
_currentState = KeyFramesStates.Disposed; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,192 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Collections; |
|||
using System.ComponentModel; |
|||
using Avalonia.Animation.Utils; |
|||
using System.Reactive.Linq; |
|||
using System.Linq; |
|||
using Avalonia.Data; |
|||
using System.Reactive.Disposables; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for KeyFrames objects
|
|||
/// </summary>
|
|||
public abstract class Animator<T> : AvaloniaList<AnimatorKeyFrame>, IAnimator |
|||
{ |
|||
/// <summary>
|
|||
/// List of type-converted keyframes.
|
|||
/// </summary>
|
|||
private Dictionary<double, (T, bool isNeutral)> _convertedKeyframes = new Dictionary<double, (T, bool)>(); |
|||
|
|||
private bool _isVerfifiedAndConverted; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the target property for the keyframe.
|
|||
/// </summary>
|
|||
public AvaloniaProperty Property { get; set; } |
|||
|
|||
public Animator() |
|||
{ |
|||
// Invalidate keyframes when changed.
|
|||
this.CollectionChanged += delegate { _isVerfifiedAndConverted = false; }; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch) |
|||
{ |
|||
if (!_isVerfifiedAndConverted) |
|||
VerifyConvertKeyFrames(animation, typeof(T)); |
|||
|
|||
return obsMatch |
|||
.Where(p => p == true) |
|||
// Ignore triggers when global timers are paused.
|
|||
.Where(p => Timing.GetGlobalPlayState() != PlayState.Pause) |
|||
.Subscribe(_ => |
|||
{ |
|||
var timerObs = RunKeyFrames(animation, control); |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the nearest pair of cue-time ordered keyframes
|
|||
/// according to the given time parameter that is relative to the
|
|||
/// total animation time and the normalized intra-keyframe pair time
|
|||
/// (i.e., the normalized time between the selected keyframes, relative to the
|
|||
/// time parameter).
|
|||
/// </summary>
|
|||
/// <param name="t">The time parameter, relative to the total animation time</param>
|
|||
protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double t) |
|||
{ |
|||
KeyValuePair<double, (T, bool)> firstCue, lastCue; |
|||
int kvCount = _convertedKeyframes.Count(); |
|||
if (kvCount > 2) |
|||
{ |
|||
if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0) |
|||
{ |
|||
firstCue = _convertedKeyframes.First(); |
|||
lastCue = _convertedKeyframes.Skip(1).First(); |
|||
} |
|||
else if (DoubleUtils.AboutEqual(t, 1.0) || t > 1.0) |
|||
{ |
|||
firstCue = _convertedKeyframes.Skip(kvCount - 2).First(); |
|||
lastCue = _convertedKeyframes.Last(); |
|||
} |
|||
else |
|||
{ |
|||
firstCue = _convertedKeyframes.Where(j => j.Key <= t).Last(); |
|||
lastCue = _convertedKeyframes.Where(j => j.Key >= t).First(); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
firstCue = _convertedKeyframes.First(); |
|||
lastCue = _convertedKeyframes.Last(); |
|||
} |
|||
|
|||
double t0 = firstCue.Key; |
|||
double t1 = lastCue.Key; |
|||
var intraframeTime = (t - t0) / (t1 - t0); |
|||
return (intraframeTime, new KeyFramePair<T>(firstCue, lastCue)); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Runs the KeyFrames Animation.
|
|||
/// </summary>
|
|||
private IDisposable RunKeyFrames(Animation animation, Animatable control) |
|||
{ |
|||
var _kfStateMach = new AnimatorStateMachine<T>(); |
|||
_kfStateMach.Initialize(animation, control, this); |
|||
|
|||
Timing.AnimationStateTimer |
|||
.TakeWhile(_ => !_kfStateMach._unsubscribe) |
|||
.Subscribe(p => |
|||
{ |
|||
_kfStateMach.Step(p, DoInterpolation); |
|||
}); |
|||
|
|||
return control.Bind(Property, _kfStateMach, BindingPriority.Animation); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Interpolates a value given the desired time.
|
|||
/// </summary>
|
|||
protected abstract T DoInterpolation(double time, T neutralValue); |
|||
|
|||
/// <summary>
|
|||
/// Verifies and converts keyframe values according to this class's target type.
|
|||
/// </summary>
|
|||
private void VerifyConvertKeyFrames(Animation animation, Type type) |
|||
{ |
|||
var typeConv = TypeDescriptor.GetConverter(type); |
|||
|
|||
foreach (AnimatorKeyFrame k in this) |
|||
{ |
|||
if (k.Value == null) |
|||
{ |
|||
throw new ArgumentNullException($"KeyFrame value can't be null."); |
|||
} |
|||
if (!typeConv.CanConvertTo(k.Value.GetType())) |
|||
{ |
|||
throw new InvalidCastException($"KeyFrame value doesnt match property type."); |
|||
} |
|||
|
|||
T convertedValue = (T)typeConv.ConvertTo(k.Value, type); |
|||
|
|||
Cue _normalizedCue = k.Cue; |
|||
|
|||
if (k.timeSpanSet) |
|||
{ |
|||
_normalizedCue = new Cue(k.KeyTime.Ticks / animation.Duration.Ticks); |
|||
} |
|||
|
|||
_convertedKeyframes.Add(_normalizedCue.CueValue, (convertedValue, false)); |
|||
} |
|||
|
|||
SortKeyFrameCues(_convertedKeyframes); |
|||
_isVerfifiedAndConverted = true; |
|||
|
|||
} |
|||
|
|||
private void SortKeyFrameCues(Dictionary<double, (T, bool)> convertedValues) |
|||
{ |
|||
bool hasStartKey, hasEndKey; |
|||
hasStartKey = hasEndKey = false; |
|||
|
|||
// Make start and end keyframe mandatory.
|
|||
foreach (var converted in _convertedKeyframes.Keys) |
|||
{ |
|||
if (DoubleUtils.AboutEqual(converted, 0.0)) |
|||
{ |
|||
hasStartKey = true; |
|||
} |
|||
else if (DoubleUtils.AboutEqual(converted, 1.0)) |
|||
{ |
|||
hasEndKey = true; |
|||
} |
|||
} |
|||
|
|||
if (!hasStartKey || !hasEndKey) |
|||
AddNeutralKeyFrames(hasStartKey, hasEndKey, _convertedKeyframes); |
|||
|
|||
_convertedKeyframes = _convertedKeyframes.OrderBy(p => p.Key) |
|||
.ToDictionary((k) => k.Key, (v) => v.Value); |
|||
} |
|||
|
|||
private void AddNeutralKeyFrames(bool hasStartKey, bool hasEndKey, Dictionary<double, (T, bool)> convertedKeyframes) |
|||
{ |
|||
if (!hasStartKey) |
|||
{ |
|||
convertedKeyframes.Add(0.0d, (default(T), true)); |
|||
} |
|||
|
|||
if (!hasEndKey) |
|||
{ |
|||
convertedKeyframes.Add(1.0d, (default(T), true)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// A Cue object for <see cref="KeyFrame"/>.
|
|||
/// </summary>
|
|||
[TypeConverter(typeof(CueTypeConverter))] |
|||
public struct Cue : IEquatable<Cue>, IEquatable<double> |
|||
{ |
|||
/// <summary>
|
|||
/// The normalized percent value, ranging from 0.0 to 1.0
|
|||
/// </summary>
|
|||
public double CueValue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Sets a new <see cref="Cue"/> object.
|
|||
/// </summary>
|
|||
/// <param name="value"></param>
|
|||
public Cue(double value) |
|||
{ |
|||
if (value <= 1 && value >= 0) |
|||
CueValue = value; |
|||
else |
|||
throw new ArgumentException($"This cue object's value should be within or equal to 0.0 and 1.0"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses a string to a <see cref="Cue"/> object.
|
|||
/// </summary>
|
|||
public static object Parse(string value, CultureInfo culture) |
|||
{ |
|||
string v = value; |
|||
|
|||
if (value.EndsWith("%")) |
|||
{ |
|||
v = v.TrimEnd('%'); |
|||
} |
|||
|
|||
if (double.TryParse(v, NumberStyles.Float, culture, out double res)) |
|||
{ |
|||
return new Cue(res / 100d); |
|||
} |
|||
else |
|||
{ |
|||
throw new FormatException($"Invalid Cue string \"{value}\""); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks for equality between two <see cref="Cue"/>s.
|
|||
/// </summary>
|
|||
/// <param name="other">The second cue.</param>
|
|||
public bool Equals(Cue other) |
|||
{ |
|||
return CueValue == other.CueValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks for equality between a <see cref="Cue"/>
|
|||
/// and a <see cref="double"/> value.
|
|||
/// </summary>
|
|||
/// <param name="other"></param>
|
|||
/// <returns></returns>
|
|||
public bool Equals(double other) |
|||
{ |
|||
return CueValue == other; |
|||
} |
|||
} |
|||
|
|||
public class CueTypeConverter : TypeConverter |
|||
{ |
|||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
|||
{ |
|||
return sourceType == typeof(string); |
|||
} |
|||
|
|||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
|||
{ |
|||
return Cue.Parse((string)value, culture); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using System.Diagnostics; |
|||
using Avalonia.Animation.Utils; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Animator that handles <see cref="double"/> properties.
|
|||
/// </summary>
|
|||
public class DoubleAnimator : Animator<double> |
|||
{ |
|||
|
|||
/// <inheritdocs/>
|
|||
protected override double DoInterpolation(double t, double neutralValue) |
|||
{ |
|||
var pair = GetKFPairAndIntraKFTime(t); |
|||
double y0, y1; |
|||
|
|||
var firstKF = pair.KFPair.FirstKeyFrame; |
|||
var secondKF = pair.KFPair.SecondKeyFrame; |
|||
|
|||
if (firstKF.Value.isNeutral) |
|||
y0 = neutralValue; |
|||
else |
|||
y0 = firstKF.Value.TargetValue; |
|||
|
|||
if (secondKF.Value.isNeutral) |
|||
y1 = neutralValue; |
|||
else |
|||
y1 = secondKF.Value.TargetValue; |
|||
|
|||
// Do linear parametric interpolation
|
|||
return y0 + (pair.IntraKFTime) * (y1 - y0); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Metadata; |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="double"/> types.
|
|||
/// </summary>
|
|||
public class DoubleTransition : Transition<double> |
|||
{ |
|||
/// <inheritdocs/>
|
|||
public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue) |
|||
{ |
|||
var delta = newValue - oldValue; |
|||
return progress |
|||
.Select(p => Easing.Ease(p) * delta + oldValue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using a overshooting cubic function.
|
|||
/// </summary>
|
|||
public class BackEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double p) |
|||
{ |
|||
return p * (p * p - Math.Sin(p * Math.PI)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piecewise overshooting cubic function.
|
|||
/// </summary>
|
|||
public class BackEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
|
|||
if (p < 0.5d) |
|||
{ |
|||
double f = 2d * p; |
|||
return 0.5d * f * (f * f - Math.Sin(f * Math.PI)); |
|||
} |
|||
else |
|||
{ |
|||
double f = (1d - (2d * p - 1d)); |
|||
return 0.5d * (1d - f * (f * f - Math.Sin(f * Math.PI))) + 0.5d; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using a overshooting cubic function.
|
|||
/// </summary>
|
|||
public class BackEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = 1d - progress; |
|||
return 1 - p * (p * p - Math.Sin(p * Math.PI)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
|
|||
using Avalonia.Animation.Utils; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using a simulated bounce function.
|
|||
/// </summary>
|
|||
public class BounceEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return 1 - BounceEaseUtils.Bounce(1 - progress); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Animation.Utils; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piecewise simulated bounce function.
|
|||
/// </summary>
|
|||
public class BounceEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
if (p < 0.5d) |
|||
{ |
|||
return 0.5f * (1 - BounceEaseUtils.Bounce(1 - (p * 2))); |
|||
} |
|||
else |
|||
{ |
|||
return 0.5f * BounceEaseUtils.Bounce(p * 2 - 1) + 0.5f; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
using Avalonia.Animation.Utils; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using a simulated bounce function.
|
|||
/// </summary>
|
|||
public class BounceEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return BounceEaseUtils.Bounce(progress); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using the shifted fourth quadrant of
|
|||
/// the unit circle.
|
|||
/// </summary>
|
|||
public class CircularEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double p) |
|||
{ |
|||
return 1d - Math.Sqrt(1d - p * p); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piecewise unit circle function.
|
|||
/// </summary>
|
|||
public class CircularEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
if (p < 0.5d) |
|||
{ |
|||
return 0.5d * (1d - Math.Sqrt(1d - 4d * p * p)); |
|||
} |
|||
else |
|||
{ |
|||
double t = 2d * p; |
|||
return 0.5d * (Math.Sqrt((3d - t) * (t - 1d)) + 1d); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using the shifted second quadrant of
|
|||
/// the unit circle.
|
|||
/// </summary>
|
|||
public class CircularEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
return Math.Sqrt((2d - p) * p); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using a cubic equation.
|
|||
/// </summary>
|
|||
public class CubicEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return progress * progress * progress; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piece-wise cubic equation.
|
|||
/// </summary>
|
|||
public class CubicEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
|
|||
if (progress < 0.5d) |
|||
{ |
|||
return 4d * p * p * p; |
|||
} |
|||
else |
|||
{ |
|||
double f = 2d * (p - 1); |
|||
return 0.5d * f * f * f + 1d; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using a cubic equation.
|
|||
/// </summary>
|
|||
public class CubicEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double f = (progress - 1d); |
|||
return f * f * f + 1d; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using Avalonia.Collections; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.Reflection; |
|||
using System.Linq; |
|||
using System.ComponentModel; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for all Easing classes.
|
|||
/// </summary>
|
|||
[TypeConverter(typeof(EasingTypeConverter))] |
|||
public abstract class Easing : IEasing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public abstract double Ease(double progress); |
|||
|
|||
static Dictionary<string, Type> _easingTypes; |
|||
|
|||
static readonly Type s_thisType = typeof(Easing); |
|||
|
|||
/// <summary>
|
|||
/// Parses a Easing type string.
|
|||
/// </summary>
|
|||
/// <param name="e">The Easing type string.</param>
|
|||
/// <returns>Returns the instance of the parsed type.</returns>
|
|||
public static Easing Parse(string e) |
|||
{ |
|||
if (_easingTypes == null) |
|||
{ |
|||
_easingTypes = new Dictionary<string, Type>(); |
|||
|
|||
// Fetch the built-in easings.
|
|||
var derivedTypes = typeof(Easing).Assembly.GetTypes() |
|||
.Where(p => p.Namespace == s_thisType.Namespace) |
|||
.Where(p => p.IsSubclassOf(s_thisType)) |
|||
.Select(p => p).ToList(); |
|||
|
|||
foreach (var easingType in derivedTypes) |
|||
_easingTypes.Add(easingType.Name, easingType); |
|||
} |
|||
|
|||
if (_easingTypes.ContainsKey(e)) |
|||
{ |
|||
var type = _easingTypes[e]; |
|||
return (Easing)Activator.CreateInstance(type); |
|||
} |
|||
else |
|||
{ |
|||
throw new FormatException($"Easing \"{e}\" was not found in {s_thisType.Namespace} namespace."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Animation.Utils; |
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using a damped sine function.
|
|||
/// </summary>
|
|||
public class ElasticEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
return Math.Sin(13d * EasingUtils.HALFPI * p) * Math.Pow(2d, 10d * (p - 1)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Animation.Utils; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piecewise damped sine function.
|
|||
/// </summary>
|
|||
public class ElasticEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
|
|||
if (p < 0.5d) |
|||
{ |
|||
double t = 2d * p; |
|||
return 0.5d * Math.Sin(13d * EasingUtils.HALFPI * t) * Math.Pow(2d, 10d * (t - 1d)); |
|||
} |
|||
else |
|||
{ |
|||
return 0.5d * (Math.Sin(-13d * EasingUtils.HALFPI * ((2d * p - 1d) + 1d)) * Math.Pow(2d, -10d * (2d * p - 1d)) + 2d); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Animation.Utils; |
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using a damped sine function.
|
|||
/// </summary>
|
|||
public class ElasticEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
return Math.Sin(-13d * EasingUtils.HALFPI * (p + 1)) * Math.Pow(2d, -10d * p) + 1d; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using a exponential function.
|
|||
/// </summary>
|
|||
public class ExponentialEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
return (p == 0.0d) ? p : Math.Pow(2d, 10d * (p - 1d)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piecewise exponential function.
|
|||
/// </summary>
|
|||
public class ExponentialEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
|
|||
if (p < 0.5d) |
|||
{ |
|||
return 0.5d * Math.Pow(2d, 20d * p - 10d); |
|||
} |
|||
else |
|||
{ |
|||
return -0.5d * Math.Pow(2d, -20d * p + 10d) + 1d; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using a exponential function.
|
|||
/// </summary>
|
|||
public class ExponentialEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
return (p == 1.0d) ? p : 1d - Math.Pow(2d, -10d * p); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Linearly eases a <see cref="double"/> value.
|
|||
/// </summary>
|
|||
public class LinearEasing : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return progress; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using a quadratic function.
|
|||
/// </summary>
|
|||
public class QuadraticEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return progress * progress; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piece-wise quadratic function.
|
|||
/// </summary>
|
|||
public class QuadraticEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
|
|||
if (progress < 0.5d) |
|||
{ |
|||
return 2d * p * p; |
|||
} |
|||
else |
|||
{ |
|||
return p * (-2d * p + 4d) - 1d; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using a quadratic function.
|
|||
/// </summary>
|
|||
public class QuadraticEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return -(progress * (progress - 2d)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using a quartic equation.
|
|||
/// </summary>
|
|||
public class QuarticEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p2 = progress * progress; |
|||
return p2 * p2; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piece-wise quartic equation.
|
|||
/// </summary>
|
|||
public class QuarticEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
|
|||
if (p < 0.5d) |
|||
{ |
|||
double p2 = p * p; |
|||
return 8d * p2 * p2; |
|||
} |
|||
else |
|||
{ |
|||
double f = p - 1d; |
|||
double f2 = f * f; |
|||
return -8d * f2 * f2 + 1d; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using a quartic equation.
|
|||
/// </summary>
|
|||
public class QuarticEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double f = progress - 1d; |
|||
double f2 = f * f; |
|||
return -f2 * f2 + 1d; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using a quartic equation.
|
|||
/// </summary>
|
|||
public class QuinticEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p2 = progress * progress; |
|||
return p2 * p2 * progress; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a piece-wise quartic equation.
|
|||
/// </summary>
|
|||
public class QuinticEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double p = progress; |
|||
if (p < 0.5d) |
|||
{ |
|||
double p2 = p * p; |
|||
return 16d * p2 * p2 * p; |
|||
} |
|||
else |
|||
{ |
|||
double f = 2d * p - 2d; |
|||
double f2 = f * f; |
|||
return 0.5d * f2 * f2 * f + 1d; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using a quartic equation.
|
|||
/// </summary>
|
|||
public class QuinticEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
double f = (progress - 1d); |
|||
double f2 = f * f; |
|||
return f2 * f2 * f + 1d; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Animation.Utils; |
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases in a <see cref="double"/> value
|
|||
/// using the quarter-wave of sine function.
|
|||
/// </summary>
|
|||
public class SineEaseIn : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return Math.Sin((progress - 1) * EasingUtils.HALFPI) + 1; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a half sine wave function.
|
|||
/// </summary>
|
|||
public class SineEaseInOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return 0.5d * (1d - Math.Cos(progress * Math.PI)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Animation.Utils; |
|||
using System; |
|||
|
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases out a <see cref="double"/> value
|
|||
/// using the quarter-wave of sine function
|
|||
/// with shifted phase.
|
|||
/// </summary>
|
|||
public class SineEaseOut : Easing |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) |
|||
{ |
|||
return Math.Sin(progress * EasingUtils.HALFPI); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public enum FillMode |
|||
{ |
|||
None, |
|||
Forward, |
|||
Backward, |
|||
Both |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Metadata; |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="float"/> types.
|
|||
/// </summary>
|
|||
public class FloatTransition : Transition<float> |
|||
{ |
|||
/// <inheritdocs/>
|
|||
public override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue) |
|||
{ |
|||
var delta = newValue - oldValue; |
|||
return progress |
|||
.Select(p => (float)Easing.Ease(p) * delta + oldValue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Interface for Animation objects
|
|||
/// </summary>
|
|||
public interface IAnimation |
|||
{ |
|||
/// <summary>
|
|||
/// Apply the animation to the specified control
|
|||
/// </summary>
|
|||
IDisposable Apply(Animatable control, IObservable<bool> match); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Avalonia.Animation |
|||
{ |
|||
public interface IAnimationSetter |
|||
{ |
|||
AvaloniaProperty Property { get; set; } |
|||
object Value { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Interface for Animator objects
|
|||
/// </summary>
|
|||
public interface IAnimator : IList<AnimatorKeyFrame> |
|||
{ |
|||
/// <summary>
|
|||
/// The target property.
|
|||
/// </summary>
|
|||
AvaloniaProperty Property {get; set;} |
|||
|
|||
/// <summary>
|
|||
/// Applies the current KeyFrame group to the specified control.
|
|||
/// </summary>
|
|||
IDisposable Apply(Animation animation, Animatable control, IObservable<bool> obsMatch); |
|||
} |
|||
} |
|||
@ -1,23 +1,16 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation |
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the interface for easing functions.
|
|||
/// Defines the interface for easing classes.
|
|||
/// </summary>
|
|||
public interface IEasing |
|||
{ |
|||
/// <summary>
|
|||
/// Returns the value of the transition for the specified progress.
|
|||
/// </summary>
|
|||
/// <param name="progress">The progress of the transition, from 0 to 1.</param>
|
|||
/// <param name="start">The start value of the transition.</param>
|
|||
/// <param name="finish">The end value of the transition.</param>
|
|||
/// <returns>
|
|||
/// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
|
|||
/// by <paramref name="progress"/>.
|
|||
/// </returns>
|
|||
object Ease(double progress, object start, object finish); |
|||
double Ease(double progress); |
|||
} |
|||
} |
|||
|
|||
@ -1,24 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the interface for easing functions.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the property being transitioned.</typeparam>
|
|||
public interface IEasing<T> : IEasing |
|||
{ |
|||
/// <summary>
|
|||
/// Returns the value of the transition for the specified progress.
|
|||
/// </summary>
|
|||
/// <param name="progress">The progress of the transition, from 0 to 1.</param>
|
|||
/// <param name="start">The start value of the transition.</param>
|
|||
/// <param name="finish">The end value of the transition.</param>
|
|||
/// <returns>
|
|||
/// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
|
|||
/// by <paramref name="progress"/>.
|
|||
/// </returns>
|
|||
T Ease(double progress, T start, T finish); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Metadata; |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Interface for Transition objects.
|
|||
/// </summary>
|
|||
public interface ITransition |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the transition to the specified <see cref="Animatable"/>.
|
|||
/// </summary>
|
|||
IDisposable Apply(Animatable control, object oldValue, object newValue); |
|||
|
|||
/// <summary>
|
|||
/// Gets the property to be animated.
|
|||
/// </summary>
|
|||
AvaloniaProperty Property { get; set; } |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Metadata; |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="int"/> types.
|
|||
/// </summary>
|
|||
public class IntegerTransition : Transition<int> |
|||
{ |
|||
/// <inheritdocs/>
|
|||
public override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue) |
|||
{ |
|||
var delta = newValue - oldValue; |
|||
return progress |
|||
.Select(p => (int)(Easing.Ease(p) * delta + oldValue)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.ComponentModel; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// Stores data regarding a specific key
|
|||
/// point and value in an animation.
|
|||
/// </summary>
|
|||
public class KeyFrame : AvaloniaList<IAnimationSetter> |
|||
{ |
|||
internal bool timeSpanSet, cueSet; |
|||
private TimeSpan _ktimeSpan; |
|||
private Cue _kCue; |
|||
|
|||
public KeyFrame() |
|||
{ |
|||
} |
|||
|
|||
public KeyFrame(IEnumerable<IAnimationSetter> items) : base(items) |
|||
{ |
|||
} |
|||
|
|||
public KeyFrame(params IAnimationSetter[] items) : base(items) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the key time of this <see cref="KeyFrame"/>.
|
|||
/// </summary>
|
|||
/// <value>The key time.</value>
|
|||
public TimeSpan KeyTime |
|||
{ |
|||
get |
|||
{ |
|||
return _ktimeSpan; |
|||
} |
|||
set |
|||
{ |
|||
if (cueSet) |
|||
{ |
|||
throw new InvalidOperationException($"You can only set either {nameof(KeyTime)} or {nameof(Cue)}."); |
|||
} |
|||
timeSpanSet = true; |
|||
_ktimeSpan = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the cue of this <see cref="KeyFrame"/>.
|
|||
/// </summary>
|
|||
/// <value>The cue.</value>
|
|||
public Cue Cue |
|||
{ |
|||
get |
|||
{ |
|||
return _kCue; |
|||
} |
|||
set |
|||
{ |
|||
if (timeSpanSet) |
|||
{ |
|||
throw new InvalidOperationException($"You can only set either {nameof(KeyTime)} or {nameof(Cue)}."); |
|||
} |
|||
cueSet = true; |
|||
_kCue = value; |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Collections; |
|||
using System.ComponentModel; |
|||
using Avalonia.Animation.Utils; |
|||
using System.Reactive.Linq; |
|||
using System.Linq; |
|||
using Avalonia.Data; |
|||
using System.Reactive.Disposables; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a pair of keyframe, usually the
|
|||
/// Start and End keyframes of a <see cref="Animator{T}"/> object.
|
|||
/// </summary>
|
|||
public struct KeyFramePair<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes this <see cref="KeyFramePair{T}"/>
|
|||
/// </summary>
|
|||
/// <param name="FirstKeyFrame"></param>
|
|||
/// <param name="LastKeyFrame"></param>
|
|||
public KeyFramePair(KeyValuePair<double, (T, bool)> FirstKeyFrame, KeyValuePair<double, (T, bool)> LastKeyFrame) : this() |
|||
{ |
|||
this.FirstKeyFrame = FirstKeyFrame; |
|||
this.SecondKeyFrame = LastKeyFrame; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// First <see cref="KeyFrame"/> object.
|
|||
/// </summary>
|
|||
public KeyValuePair<double, (T TargetValue, bool isNeutral)> FirstKeyFrame { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Second <see cref="KeyFrame"/> object.
|
|||
/// </summary>
|
|||
public KeyValuePair<double, (T TargetValue, bool isNeutral)> SecondKeyFrame { get; private set; } |
|||
} |
|||
} |
|||
@ -1,41 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Linearly eases a double value.
|
|||
/// </summary>
|
|||
public class LinearDoubleEasing : IEasing<double> |
|||
{ |
|||
/// <summary>
|
|||
/// Returns the value of the transition for the specified progress.
|
|||
/// </summary>
|
|||
/// <param name="progress">The progress of the transition, from 0 to 1.</param>
|
|||
/// <param name="start">The start value of the transition.</param>
|
|||
/// <param name="finish">The end value of the transition.</param>
|
|||
/// <returns>
|
|||
/// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
|
|||
/// by <paramref name="progress"/>.
|
|||
/// </returns>
|
|||
public double Ease(double progress, double start, double finish) |
|||
{ |
|||
return ((finish - start) * progress) + start; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the value of the transition for the specified progress.
|
|||
/// </summary>
|
|||
/// <param name="progress">The progress of the transition, from 0 to 1.</param>
|
|||
/// <param name="start">The start value of the transition.</param>
|
|||
/// <param name="finish">The end value of the transition.</param>
|
|||
/// <returns>
|
|||
/// A value between <paramref name="start"/> and <paramref name="finish"/> as determined
|
|||
/// by <paramref name="progress"/>.
|
|||
/// </returns>
|
|||
object IEasing.Ease(double progress, object start, object finish) |
|||
{ |
|||
return Ease(progress, (double)start, (double)finish); |
|||
} |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Returns a linear <see cref="IEasing"/> for the specified type.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Unfortunately this class is needed as there's no way to create a true generic easing
|
|||
/// function at compile time, as mathematical operators don't have an interface.
|
|||
/// </remarks>
|
|||
public static class LinearEasing |
|||
{ |
|||
/// <summary>
|
|||
/// A linear easing function for the specified type.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type.</typeparam>
|
|||
/// <returns>An easing function.</returns>
|
|||
public static IEasing<T> For<T>() |
|||
{ |
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
return (IEasing<T>)new LinearDoubleEasing(); |
|||
} |
|||
else |
|||
{ |
|||
throw new NotSupportedException( |
|||
$"Don't know how to create a LinearEasing for type '{typeof(T).FullName}'."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Determines the playback state of an animation.
|
|||
/// </summary>
|
|||
public enum PlayState |
|||
{ |
|||
/// <summary>
|
|||
/// The animation is running.
|
|||
/// </summary>
|
|||
Run, |
|||
|
|||
/// <summary>
|
|||
/// The animation is paused.
|
|||
/// </summary>
|
|||
Pause, |
|||
|
|||
/// <summary>
|
|||
/// The animation is stopped.
|
|||
/// </summary>
|
|||
Stop |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Determines the playback direction of an animation.
|
|||
/// </summary>
|
|||
public enum PlaybackDirection |
|||
{ |
|||
/// <summary>
|
|||
/// The animation is played normally.
|
|||
/// </summary>
|
|||
Normal, |
|||
|
|||
/// <summary>
|
|||
/// The animation is played in reverse direction.
|
|||
/// </summary>
|
|||
Reverse, |
|||
|
|||
/// <summary>
|
|||
/// The animation is played forwards first, then backwards.
|
|||
/// </summary>
|
|||
Alternate, |
|||
|
|||
/// <summary>
|
|||
/// The animation is played backwards first, then forwards.
|
|||
/// </summary>
|
|||
AlternateReverse |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Metadata; |
|||
using System.Reflection; |
|||
|
|||
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")] |
|||
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")] |
|||
@ -1,50 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines how a property should be animated using a transition.
|
|||
/// </summary>
|
|||
public class PropertyTransition |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PropertyTransition"/> class.
|
|||
/// </summary>
|
|||
/// <param name="property">The property to be animated/</param>
|
|||
/// <param name="duration">The duration of the animation.</param>
|
|||
/// <param name="easing">The easing function to use.</param>
|
|||
public PropertyTransition(AvaloniaProperty property, TimeSpan duration, IEasing easing) |
|||
{ |
|||
Property = property; |
|||
Duration = duration; |
|||
Easing = easing; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the property to be animated.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The property to be animated.
|
|||
/// </value>
|
|||
public AvaloniaProperty Property { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the duration of the animation.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The duration of the animation.
|
|||
/// </value>
|
|||
public TimeSpan Duration { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the easing function used.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The easing function.
|
|||
/// </value>
|
|||
public IEasing Easing { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,202 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Utilities; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the valid modes for a <see cref="RepeatCount"/>.
|
|||
/// </summary>
|
|||
public enum RepeatType |
|||
{ |
|||
None, |
|||
Repeat, |
|||
Loop |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines the number of iterations of an animation.
|
|||
/// Also defines its repeat behavior.
|
|||
/// </summary>
|
|||
[TypeConverter(typeof(RepeatCountTypeConverter))] |
|||
public struct RepeatCount : IEquatable<RepeatCount> |
|||
{ |
|||
private readonly RepeatType _type; |
|||
private readonly ulong _value; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RepeatCount"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="value">The number of iterations of an animation.</param>
|
|||
public RepeatCount(ulong value) |
|||
: this(value, RepeatType.Repeat) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RepeatCount"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="value">The size of the RepeatCount.</param>
|
|||
/// <param name="type">The unit of the RepeatCount.</param>
|
|||
public RepeatCount(ulong value, RepeatType type) |
|||
{ |
|||
if (type < RepeatType.None || type > RepeatType.Loop) |
|||
{ |
|||
throw new ArgumentException("Invalid value", "type"); |
|||
} |
|||
|
|||
_type = type; |
|||
_value = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets an instance of <see cref="RepeatCount"/> that indicates that an animation
|
|||
/// should repeat forever.
|
|||
/// </summary>
|
|||
public static RepeatCount Loop => new RepeatCount(0, RepeatType.Loop); |
|||
|
|||
/// <summary>
|
|||
/// Gets an instance of <see cref="RepeatCount"/> that indicates that an animation
|
|||
/// should not repeat.
|
|||
/// </summary>
|
|||
public static RepeatCount None => new RepeatCount(0, RepeatType.None); |
|||
|
|||
/// <summary>
|
|||
/// Gets the unit of the <see cref="RepeatCount"/>.
|
|||
/// </summary>
|
|||
public RepeatType RepeatType => _type; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates whether the <see cref="RepeatCount"/> is set to loop.
|
|||
/// </summary>
|
|||
public bool IsLoop => _type == RepeatType.Loop; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value that indicates whether the <see cref="RepeatCount"/> is set to not repeat.
|
|||
/// </summary>
|
|||
public bool IsNone => _type == RepeatType.None; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of repeat iterations.
|
|||
/// </summary>
|
|||
public ulong Value => _value; |
|||
|
|||
/// <summary>
|
|||
/// Compares two RepeatCount structures for equality.
|
|||
/// </summary>
|
|||
/// <param name="a">The first RepeatCount.</param>
|
|||
/// <param name="b">The second RepeatCount.</param>
|
|||
/// <returns>True if the structures are equal, otherwise false.</returns>
|
|||
public static bool operator ==(RepeatCount a, RepeatCount b) |
|||
{ |
|||
return (a.IsNone && b.IsNone) && (a.IsLoop && b.IsLoop) |
|||
|| (a._value == b._value && a._type == b._type); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two RepeatCount structures for inequality.
|
|||
/// </summary>
|
|||
/// <param name="rc1">The first RepeatCount.</param>
|
|||
/// <param name="rc2">The first RepeatCount.</param>
|
|||
/// <returns>True if the structures are unequal, otherwise false.</returns>
|
|||
public static bool operator !=(RepeatCount rc1, RepeatCount rc2) |
|||
{ |
|||
return !(rc1 == rc2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the <see cref="RepeatCount"/> is equal to the specified object.
|
|||
/// </summary>
|
|||
/// <param name="o">The object with which to test equality.</param>
|
|||
/// <returns>True if the objects are equal, otherwise false.</returns>
|
|||
public override bool Equals(object o) |
|||
{ |
|||
if (o == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!(o is RepeatCount)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return this == (RepeatCount)o; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two RepeatCount structures for equality.
|
|||
/// </summary>
|
|||
/// <param name="RepeatCount">The structure with which to test equality.</param>
|
|||
/// <returns>True if the structures are equal, otherwise false.</returns>
|
|||
public bool Equals(RepeatCount RepeatCount) |
|||
{ |
|||
return this == RepeatCount; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a hash code for the RepeatCount.
|
|||
/// </summary>
|
|||
/// <returns>The hash code.</returns>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return _value.GetHashCode() ^ _type.GetHashCode(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a string representation of the <see cref="RepeatCount"/>.
|
|||
/// </summary>
|
|||
/// <returns>The string representation.</returns>
|
|||
public override string ToString() |
|||
{ |
|||
if (IsLoop) |
|||
{ |
|||
return "Auto"; |
|||
} |
|||
else if (IsNone) |
|||
{ |
|||
return "None"; |
|||
} |
|||
|
|||
string s = _value.ToString(); |
|||
return s; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses a string to return a <see cref="RepeatCount"/>.
|
|||
/// </summary>
|
|||
/// <param name="s">The string.</param>
|
|||
/// <returns>The <see cref="RepeatCount"/>.</returns>
|
|||
public static RepeatCount Parse(string s) |
|||
{ |
|||
s = s.ToUpperInvariant().Trim(); |
|||
|
|||
if (s == "NONE") |
|||
{ |
|||
return None; |
|||
} |
|||
else if (s.EndsWith("LOOP")) |
|||
{ |
|||
return Loop; |
|||
} |
|||
else |
|||
{ |
|||
if(s.StartsWith("-")) |
|||
throw new InvalidCastException("RepeatCount can't be a negative number."); |
|||
|
|||
var value = ulong.Parse(s, CultureInfo.InvariantCulture); |
|||
|
|||
if (value == 1) |
|||
return None; |
|||
|
|||
return new RepeatCount(value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Data; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Provides global timing functions for animations.
|
|||
/// </summary>
|
|||
public static class Timing |
|||
{ |
|||
static ulong _transitionsFrameCount; |
|||
static PlayState _globalState = PlayState.Run; |
|||
|
|||
/// <summary>
|
|||
/// The number of frames per second.
|
|||
/// </summary>
|
|||
public const int FramesPerSecond = 60; |
|||
|
|||
/// <summary>
|
|||
/// The time span of each frame.
|
|||
/// </summary>
|
|||
internal static readonly TimeSpan FrameTick = TimeSpan.FromSeconds(1.0 / FramesPerSecond); |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="Timing"/> class.
|
|||
/// </summary>
|
|||
static Timing() |
|||
{ |
|||
var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); |
|||
|
|||
AnimationStateTimer = globalTimer |
|||
.Select(_ => |
|||
{ |
|||
return _globalState; |
|||
}) |
|||
.Publish() |
|||
.RefCount(); |
|||
|
|||
TransitionsTimer = globalTimer |
|||
.Select(p => _transitionsFrameCount++) |
|||
.Publish() |
|||
.RefCount(); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Sets the animation play state for all animations
|
|||
/// </summary>
|
|||
public static void SetGlobalPlayState(PlayState playState) |
|||
{ |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
_globalState = playState; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the animation play state for all animations
|
|||
/// </summary>
|
|||
public static PlayState GetGlobalPlayState() |
|||
{ |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
return _globalState; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the animation timer.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The animation timer triggers usually at 60 times per second or as
|
|||
/// defined in <see cref="FramesPerSecond"/>.
|
|||
/// The parameter passed to a subsciber is the current playstate of the animation.
|
|||
/// </remarks>
|
|||
internal static IObservable<PlayState> AnimationStateTimer |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the transitions timer.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The transitions timer increments usually 60 times per second as
|
|||
/// defined in <see cref="FramesPerSecond"/>.
|
|||
/// The parameter passed to a subsciber is the number of frames since the animation system was
|
|||
/// initialized.
|
|||
/// </remarks>
|
|||
public static IObservable<ulong> TransitionsTimer |
|||
{ |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a timer that fires every frame for the specified duration with delay.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// An observable that notifies the subscriber of the progress along the transition.
|
|||
/// </returns>
|
|||
/// <remarks>
|
|||
/// The parameter passed to the subscriber is the progress along the transition, with
|
|||
/// 0 being the start and 1 being the end. The observable is guaranteed to fire 0
|
|||
/// immediately on subscribe and 1 at the end of the duration.
|
|||
/// </remarks>
|
|||
public static IObservable<double> GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan)) |
|||
{ |
|||
var startTime = _transitionsFrameCount; |
|||
var _duration = (ulong)(duration.Ticks / FrameTick.Ticks); |
|||
var endTime = startTime + _duration; |
|||
|
|||
return TransitionsTimer |
|||
.TakeWhile(x => x < endTime) |
|||
.Select(x => (double)(x - startTime) / _duration) |
|||
.StartWith(0.0) |
|||
.Concat(Observable.Return(1.0)); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Metadata; |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Animation.Easings; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Defines how a property should be animated using a transition.
|
|||
/// </summary>
|
|||
public abstract class Transition<T> : AvaloniaObject, ITransition |
|||
{ |
|||
private AvaloniaProperty _prop; |
|||
private Easing _easing; |
|||
|
|||
/// <summary>
|
|||
/// Gets the duration of the animation.
|
|||
/// </summary>
|
|||
public TimeSpan Duration { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the easing class to be used.
|
|||
/// </summary>
|
|||
public Easing Easing |
|||
{ |
|||
get |
|||
{ |
|||
return _easing ?? (_easing = new LinearEasing()); |
|||
} |
|||
set |
|||
{ |
|||
_easing = value; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdocs/>
|
|||
public AvaloniaProperty Property |
|||
{ |
|||
get |
|||
{ |
|||
return _prop; |
|||
} |
|||
set |
|||
{ |
|||
if (!(value.PropertyType.IsAssignableFrom(typeof(T)))) |
|||
throw new InvalidCastException |
|||
($"Invalid property type \"{typeof(T).Name}\" for this transition: {GetType().Name}."); |
|||
|
|||
_prop = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Apply interpolation to the property.
|
|||
/// </summary>
|
|||
public abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue); |
|||
|
|||
/// <inheritdocs/>
|
|||
public virtual IDisposable Apply(Animatable control, object oldValue, object newValue) |
|||
{ |
|||
var transition = DoTransition(Timing.GetTransitionsTimer(control, Duration, TimeSpan.Zero), (T)oldValue, (T)newValue).Select(p => (object)p); |
|||
return control.Bind(Property, transition, Data.BindingPriority.Animation); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue