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 Android.Views; |
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
|
|
||||
namespace Avalonia.Android.Platform.Specific |
namespace Avalonia.Android.Platform.Specific |
||||
{ |
{ |
||||
public interface IAndroidView |
public interface IAndroidView |
||||
{ |
{ |
||||
|
|
||||
|
|
||||
View View { get; } |
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