committed by
GitHub
600 changed files with 4508 additions and 3164 deletions
@ -0,0 +1,13 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui"> |
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Classes="h1">ListBox</TextBlock> |
|||
<TextBlock Classes="h2">Hosts a collection of ListBoxItem.</TextBlock> |
|||
|
|||
<StackPanel Orientation="Horizontal" |
|||
Margin="0,16,0,0" |
|||
HorizontalAlignment="Center" |
|||
Spacing="16"> |
|||
<ListBox Items="{Binding}" Width="250" Height="350"></ListBox> |
|||
</StackPanel> |
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class ListBoxPage : UserControl |
|||
{ |
|||
public ListBoxPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
DataContext = Enumerable.Range(1, 10).Select(i => $"Item {i}" ) |
|||
.ToArray(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -1,16 +1,9 @@ |
|||
using Android.Graphics; |
|||
using Android.Views; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Android.Platform.Specific |
|||
{ |
|||
public interface IAndroidView |
|||
{ |
|||
|
|||
|
|||
View View { get; } |
|||
} |
|||
|
|||
|
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,199 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Animation.Utils; |
|||
using Avalonia.Data; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Handles interpolation and time-related functions
|
|||
/// for keyframe animations.
|
|||
/// </summary>
|
|||
internal class AnimationInstance<T> : SingleSubscriberObservableBase<T> |
|||
{ |
|||
private T _lastInterpValue; |
|||
private T _firstKFValue; |
|||
private long _repeatCount; |
|||
private double _currentIteration; |
|||
private bool _isLooping; |
|||
private bool _gotFirstKFValue; |
|||
private bool _iterationDelay; |
|||
private FillMode _fillMode; |
|||
private PlaybackDirection _animationDirection; |
|||
private Animator<T> _parent; |
|||
private Animatable _targetControl; |
|||
private T _neutralValue; |
|||
private double _speedRatio; |
|||
private TimeSpan _delay; |
|||
private TimeSpan _duration; |
|||
private Easings.Easing _easeFunc; |
|||
private Action _onCompleteAction; |
|||
private Func<double, T, T> _interpolator; |
|||
private IDisposable _timerSubscription; |
|||
private readonly IClock _baseClock; |
|||
private IClock _clock; |
|||
|
|||
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator) |
|||
{ |
|||
if (animation.SpeedRatio <= 0) |
|||
throw new InvalidOperationException("Speed ratio cannot be negative or zero."); |
|||
|
|||
if (animation.Duration.TotalSeconds <= 0) |
|||
throw new InvalidOperationException("Duration cannot be negative or zero."); |
|||
|
|||
_parent = animator; |
|||
_easeFunc = animation.Easing; |
|||
_targetControl = control; |
|||
_neutralValue = (T)_targetControl.GetValue(_parent.Property); |
|||
|
|||
_speedRatio = animation.SpeedRatio; |
|||
|
|||
_delay = animation.Delay; |
|||
_duration = animation.Duration; |
|||
_iterationDelay = animation.DelayBetweenIterations; |
|||
|
|||
switch (animation.RepeatCount.RepeatType) |
|||
{ |
|||
case RepeatType.None: |
|||
_repeatCount = 1; |
|||
break; |
|||
case RepeatType.Loop: |
|||
_isLooping = true; |
|||
break; |
|||
case RepeatType.Repeat: |
|||
_repeatCount = (long)animation.RepeatCount.Value; |
|||
break; |
|||
} |
|||
|
|||
_animationDirection = animation.PlaybackDirection; |
|||
_fillMode = animation.FillMode; |
|||
_onCompleteAction = OnComplete; |
|||
_interpolator = Interpolator; |
|||
_baseClock = baseClock; |
|||
} |
|||
|
|||
protected override void Unsubscribed() |
|||
{ |
|||
_timerSubscription?.Dispose(); |
|||
_clock.PlayState = PlayState.Stop; |
|||
} |
|||
|
|||
protected override void Subscribed() |
|||
{ |
|||
_clock = new Clock(_baseClock); |
|||
_timerSubscription = _clock.Subscribe(Step); |
|||
} |
|||
|
|||
public void Step(TimeSpan frameTick) |
|||
{ |
|||
try |
|||
{ |
|||
InternalStep(frameTick); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
PublishError(e); |
|||
} |
|||
} |
|||
|
|||
private void DoComplete() |
|||
{ |
|||
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) |
|||
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); |
|||
|
|||
_onCompleteAction?.Invoke(); |
|||
PublishCompleted(); |
|||
} |
|||
|
|||
private void DoDelay() |
|||
{ |
|||
if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both) |
|||
if (_currentIteration == 0) |
|||
PublishNext(_firstKFValue); |
|||
else |
|||
PublishNext(_lastInterpValue); |
|||
} |
|||
|
|||
private void DoPlayStates() |
|||
{ |
|||
if (_clock.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop) |
|||
DoComplete(); |
|||
|
|||
if (!_gotFirstKFValue) |
|||
{ |
|||
_firstKFValue = (T)_parent.First().Value; |
|||
_gotFirstKFValue = true; |
|||
} |
|||
} |
|||
|
|||
private void InternalStep(TimeSpan time) |
|||
{ |
|||
DoPlayStates(); |
|||
var delayEndpoint = _delay; |
|||
var iterationEndpoint = delayEndpoint + _duration; |
|||
|
|||
//determine if time is currently in the first iteration.
|
|||
if (time >= TimeSpan.Zero & time <= iterationEndpoint) |
|||
{ |
|||
_currentIteration = 1; |
|||
} |
|||
else if (time > iterationEndpoint) |
|||
{ |
|||
//Subtract first iteration to properly get the subsequent iteration time
|
|||
time -= iterationEndpoint; |
|||
|
|||
if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) |
|||
{ |
|||
delayEndpoint = TimeSpan.Zero; |
|||
iterationEndpoint = _duration; |
|||
} |
|||
|
|||
//Calculate the current iteration number
|
|||
_currentIteration = (int)Math.Floor((double)((double)time.Ticks / iterationEndpoint.Ticks)) + 2; |
|||
} |
|||
else |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
time = TimeSpan.FromTicks((long)(time.Ticks % iterationEndpoint.Ticks)); |
|||
|
|||
if (!_isLooping) |
|||
{ |
|||
if ((_currentIteration > _repeatCount) || (time > iterationEndpoint)) |
|||
DoComplete(); |
|||
} |
|||
|
|||
// Determine if the current iteration should have its normalized time inverted.
|
|||
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 (delayEndpoint > TimeSpan.Zero & time < delayEndpoint) |
|||
{ |
|||
DoDelay(); |
|||
} |
|||
else |
|||
{ |
|||
// Offset the delay time
|
|||
time -= delayEndpoint; |
|||
iterationEndpoint -= delayEndpoint; |
|||
|
|||
// Normalize time
|
|||
var interpVal = (double)time.Ticks / iterationEndpoint.Ticks; |
|||
|
|||
if (isCurIterReverse) |
|||
interpVal = 1 - interpVal; |
|||
|
|||
// Ease and interpolate
|
|||
var easedTime = _easeFunc.Ease(interpVal); |
|||
_lastInterpValue = _interpolator(easedTime, _neutralValue); |
|||
|
|||
PublishNext(_lastInterpValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,273 +0,0 @@ |
|||
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; |
|||
private readonly Action _onComplete; |
|||
|
|||
[Flags] |
|||
private enum KeyFramesStates |
|||
{ |
|||
Initialize, |
|||
DoDelay, |
|||
DoRun, |
|||
RunForwards, |
|||
RunBackwards, |
|||
RunApplyValue, |
|||
RunComplete, |
|||
Pause, |
|||
Stop, |
|||
Disposed |
|||
} |
|||
|
|||
public AnimatorStateMachine(Animation animation, Animatable control, Animator<T> animator, Action onComplete) |
|||
{ |
|||
_parent = animator; |
|||
_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; |
|||
_onComplete = onComplete; |
|||
} |
|||
|
|||
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; |
|||
|
|||
bool handled = false; |
|||
|
|||
while (!handled) |
|||
{ |
|||
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; |
|||
} |
|||
else |
|||
{ |
|||
handled = true; |
|||
_delayFrameCount++; |
|||
} |
|||
break; |
|||
|
|||
case KeyFramesStates.DoRun: |
|||
|
|||
if (_isReversed) |
|||
_currentState = KeyFramesStates.RunBackwards; |
|||
else |
|||
_currentState = KeyFramesStates.RunForwards; |
|||
|
|||
break; |
|||
|
|||
case KeyFramesStates.RunForwards: |
|||
|
|||
if (_durationFrameCount > _durationTotalFrameCount) |
|||
{ |
|||
_currentState = KeyFramesStates.RunComplete; |
|||
} |
|||
else |
|||
{ |
|||
_tempDuration = (double)_durationFrameCount / _durationTotalFrameCount; |
|||
_currentState = KeyFramesStates.RunApplyValue; |
|||
|
|||
} |
|||
break; |
|||
|
|||
case KeyFramesStates.RunBackwards: |
|||
|
|||
if (_durationFrameCount > _durationTotalFrameCount) |
|||
{ |
|||
_currentState = KeyFramesStates.RunComplete; |
|||
} |
|||
else |
|||
{ |
|||
_tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount; |
|||
_currentState = KeyFramesStates.RunApplyValue; |
|||
} |
|||
break; |
|||
|
|||
case KeyFramesStates.RunApplyValue: |
|||
|
|||
_easedTime = _targetAnimation.Easing.Ease(_tempDuration); |
|||
|
|||
_durationFrameCount++; |
|||
_lastInterpValue = Interpolator(_easedTime, _neutralValue); |
|||
_targetObserver.OnNext(_lastInterpValue); |
|||
_currentState = KeyFramesStates.DoRun; |
|||
handled = true; |
|||
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; |
|||
break; |
|||
|
|||
case KeyFramesStates.Stop: |
|||
|
|||
if (_fillMode == FillMode.Forward |
|||
|| _fillMode == FillMode.Both) |
|||
{ |
|||
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); |
|||
} |
|||
|
|||
_targetObserver.OnCompleted(); |
|||
_onComplete?.Invoke(); |
|||
Dispose(); |
|||
handled = true; |
|||
break; |
|||
default: |
|||
handled = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IDisposable Subscribe(IObserver<object> observer) |
|||
{ |
|||
_targetObserver = observer; |
|||
return this; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_unsubscribe = true; |
|||
_currentState = KeyFramesStates.Disposed; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using System.Text; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public class Clock : ClockBase |
|||
{ |
|||
public static IClock GlobalClock => AvaloniaLocator.Current.GetService<IGlobalClock>(); |
|||
|
|||
private IDisposable _parentSubscription; |
|||
|
|||
public Clock() |
|||
:this(GlobalClock) |
|||
{ |
|||
} |
|||
|
|||
public Clock(IClock parent) |
|||
{ |
|||
_parentSubscription = parent.Subscribe(Pulse); |
|||
} |
|||
|
|||
protected override void Stop() |
|||
{ |
|||
_parentSubscription?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using System.Text; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public class ClockBase : IClock |
|||
{ |
|||
private ClockObservable _observable; |
|||
|
|||
private IObservable<TimeSpan> _connectedObservable; |
|||
|
|||
private TimeSpan? _previousTime; |
|||
private TimeSpan _internalTime; |
|||
|
|||
protected ClockBase() |
|||
{ |
|||
_observable = new ClockObservable(); |
|||
_connectedObservable = _observable.Publish().RefCount(); |
|||
} |
|||
|
|||
protected bool HasSubscriptions => _observable.HasSubscriptions; |
|||
|
|||
public PlayState PlayState { get; set; } |
|||
|
|||
protected void Pulse(TimeSpan systemTime) |
|||
{ |
|||
if (!_previousTime.HasValue) |
|||
{ |
|||
_previousTime = systemTime; |
|||
_internalTime = TimeSpan.Zero; |
|||
} |
|||
else |
|||
{ |
|||
if (PlayState == PlayState.Pause) |
|||
{ |
|||
_previousTime = systemTime; |
|||
return; |
|||
} |
|||
var delta = systemTime - _previousTime; |
|||
_internalTime += delta.Value; |
|||
_previousTime = systemTime; |
|||
} |
|||
|
|||
_observable.Pulse(_internalTime); |
|||
|
|||
if (PlayState == PlayState.Stop) |
|||
{ |
|||
Stop(); |
|||
} |
|||
} |
|||
|
|||
protected virtual void Stop() |
|||
{ |
|||
} |
|||
|
|||
public IDisposable Subscribe(IObserver<TimeSpan> observer) |
|||
{ |
|||
return _connectedObservable.Subscribe(observer); |
|||
} |
|||
|
|||
private class ClockObservable : LightweightObservableBase<TimeSpan> |
|||
{ |
|||
public bool HasSubscriptions { get; private set; } |
|||
public void Pulse(TimeSpan time) => PublishNext(time); |
|||
protected override void Initialize() => HasSubscriptions = true; |
|||
protected override void Deinitialize() => HasSubscriptions = false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Animation.Utils; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Data; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Manages the lifetime of animation instances as determined by its selector state.
|
|||
/// </summary>
|
|||
internal class DisposeAnimationInstanceSubject<T> : IObserver<bool>, IDisposable |
|||
{ |
|||
private IDisposable _lastInstance; |
|||
private bool _lastMatch; |
|||
private Animator<T> _animator; |
|||
private Animation _animation; |
|||
private Animatable _control; |
|||
private Action _onComplete; |
|||
private IClock _clock; |
|||
|
|||
public DisposeAnimationInstanceSubject(Animator<T> animator, Animation animation, Animatable control, IClock clock, Action onComplete) |
|||
{ |
|||
this._animator = animator; |
|||
this._animation = animation; |
|||
this._control = control; |
|||
this._onComplete = onComplete; |
|||
this._clock = clock; |
|||
} |
|||
|
|||
|
|||
public void Dispose() |
|||
{ |
|||
_lastInstance?.Dispose(); |
|||
} |
|||
|
|||
public void OnCompleted() |
|||
{ |
|||
} |
|||
|
|||
public void OnError(Exception error) |
|||
{ |
|||
_lastInstance?.Dispose(); |
|||
} |
|||
|
|||
void IObserver<bool>.OnNext(bool matchVal) |
|||
{ |
|||
if (matchVal != _lastMatch) |
|||
{ |
|||
_lastInstance?.Dispose(); |
|||
if (matchVal) |
|||
{ |
|||
_lastInstance = _animator.Run(_animation, _control, _clock, _onComplete); |
|||
} |
|||
_lastMatch = matchVal; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public interface IClock : IObservable<TimeSpan> |
|||
{ |
|||
PlayState PlayState { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public interface IGlobalClock : IClock |
|||
{ |
|||
} |
|||
} |
|||
@ -1,123 +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>
|
|||
/// 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,55 @@ |
|||
// 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; |
|||
using Avalonia.Animation.Utils; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Handles the timing and lifetime of a <see cref="Transition{T}"/>.
|
|||
/// </summary>
|
|||
internal class TransitionInstance : SingleSubscriberObservableBase<double> |
|||
{ |
|||
private IDisposable _timerSubscription; |
|||
private TimeSpan _duration; |
|||
private readonly IClock _baseClock; |
|||
private IClock _clock; |
|||
|
|||
public TransitionInstance(IClock clock, TimeSpan Duration) |
|||
{ |
|||
_duration = Duration; |
|||
_baseClock = clock; |
|||
} |
|||
|
|||
private void TimerTick(TimeSpan t) |
|||
{ |
|||
var interpVal = (double)t.Ticks / _duration.Ticks; |
|||
|
|||
if (interpVal > 1d || interpVal < 0d) |
|||
{ |
|||
PublishCompleted(); |
|||
return; |
|||
} |
|||
|
|||
PublishNext(interpVal); |
|||
} |
|||
|
|||
protected override void Unsubscribed() |
|||
{ |
|||
_timerSubscription?.Dispose(); |
|||
_clock.PlayState = PlayState.Stop; |
|||
} |
|||
|
|||
protected override void Subscribed() |
|||
{ |
|||
_clock = new Clock(_baseClock); |
|||
_timerSubscription = _clock.Subscribe(TimerTick); |
|||
PublishNext(0.0d); |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +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.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Animation.Utils |
|||
{ |
|||
internal static class DoubleUtils |
|||
{ |
|||
internal static bool AboutEqual(double x, double y) |
|||
{ |
|||
double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15; |
|||
return Math.Abs(x - y) <= epsilon; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue