@ -1,10 +1,10 @@
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Linq ;
using System.Reactive.Linq ;
using Avalonia.Collections ;
using System.Collections.Specialized ;
using Avalonia.Data ;
using Avalonia.Animation.Animators ;
# nullable enable
namespace Avalonia.Animation
{
@ -13,9 +13,24 @@ namespace Avalonia.Animation
/// </summary>
public class Animatable : AvaloniaObject
{
/// <summary>
/// Defines the <see cref="Clock"/> property.
/// </summary>
public static readonly StyledProperty < IClock > ClockProperty =
AvaloniaProperty . Register < Animatable , IClock > ( nameof ( Clock ) , inherits : true ) ;
/// <summary>
/// Defines the <see cref="Transitions"/> property.
/// </summary>
public static readonly StyledProperty < Transitions ? > TransitionsProperty =
AvaloniaProperty . Register < Animatable , Transitions ? > ( nameof ( Transitions ) ) ;
private bool _ transitionsEnabled = true ;
private Dictionary < ITransition , TransitionState > ? _ transitionState ;
/// <summary>
/// Gets or sets the clock which controls the animations on the control.
/// </summary>
public IClock Clock
{
get = > GetValue ( ClockProperty ) ;
@ -23,72 +38,194 @@ namespace Avalonia.Animation
}
/// <summary>
/// Defines the <see cref="Transitions"/> property .
/// Gets or sets the property transitions for the control .
/// </summary>
public static readonly DirectProperty < Animatable , Transitions > TransitionsProperty =
AvaloniaProperty . RegisterDirect < Animatable , Transitions > (
nameof ( Transitions ) ,
o = > o . Transitions ,
( o , v ) = > o . Transitions = v ) ;
public Transitions ? Transitions
{
get = > GetValue ( TransitionsProperty ) ;
set = > SetValue ( TransitionsProperty , value ) ;
}
private Transitions _ transitions ;
/// <summary>
/// Enables transitions for the control.
/// </summary>
/// <remarks>
/// This method should not be called from user code, it will be called automatically by the framework
/// when a control is added to the visual tree.
/// </remarks>
protected void EnableTransitions ( )
{
if ( ! _ transitionsEnabled )
{
_ transitionsEnabled = true ;
private Dictionary < AvaloniaProperty , IDisposable > _ previousTransitions ;
if ( Transitions is object )
{
AddTransitions ( Transitions ) ;
}
}
}
/// <summary>
/// Gets or sets the property transitions for the control.
/// Disables transitions for the control.
/// </summary>
public Transitions Transitions
/// <remarks>
/// This method should not be called from user code, it will be called automatically by the framework
/// when a control is added to the visual tree.
/// </remarks>
protected void DisableTransitions ( )
{
if ( _ transitionsEnabled )
{
_ transitionsEnabled = false ;
if ( Transitions is object )
{
RemoveTransitions ( Transitions ) ;
}
}
}
protected sealed override void OnPropertyChangedCore < T > ( AvaloniaPropertyChangedEventArgs < T > change )
{
get
if ( chan ge. Proper ty = = TransitionsProperty & & change . IsEffectiveValueChange )
{
if ( _ transitions is null )
_ transitions = new Transitions ( ) ;
var oldTransitions = change . OldValue . GetValueOrDefault < Transitions > ( ) ;
var newTransitions = change . NewValue . GetValueOrDefault < Transitions > ( ) ;
if ( _ previousTransitions is null )
_ previousTransitions = new Dictionary < AvaloniaProperty , IDisposable > ( ) ;
if ( oldTransitions is object )
{
oldTransitions . CollectionChanged - = TransitionsCollectionChanged ;
RemoveTransitions ( oldTransitions ) ;
}
return _ transitions ;
if ( newTransitions is object )
{
newTransitions . CollectionChanged + = TransitionsCollectionChanged ;
AddTransitions ( newTransitions ) ;
}
}
set
else if ( _ transitionsEnabled & &
Transitions is object & &
_ transitionState is object & &
! change . Property . IsDirect & &
change . Priority > BindingPriority . Animation )
{
if ( value is null )
return ;
foreach ( var transition in Transitions )
{
if ( transition . Property = = change . Property )
{
var state = _ transitionState [ transition ] ;
var oldValue = state . BaseValue ;
var newValue = GetAnimationBaseValue ( transition . Property ) ;
if ( _ previousTransitions is null )
_ previousTransitions = new Dictionary < AvaloniaProperty , IDisposable > ( ) ;
if ( ! Equals ( oldValue , newValue ) )
{
state . BaseValue = newValue ;
SetAndRaise ( TransitionsProperty , ref _ transitions , value ) ;
// We need to transition from the current animated value if present,
// instead of the old base value.
var animatedValue = GetValue ( transition . Property ) ;
if ( ! Equals ( newValue , animatedValue ) )
{
oldValue = animatedValue ;
}
state . Instance ? . Dispose ( ) ;
state . Instance = transition . Apply (
this ,
Clock ? ? AvaloniaLocator . Current . GetService < IGlobalClock > ( ) ,
oldValue ,
newValue ) ;
return ;
}
}
}
}
base . OnPropertyChangedCore ( change ) ;
}
private void TransitionsCollectionChanged ( object sender , NotifyCollectionChangedEventArgs e )
{
if ( ! _ transitionsEnabled )
{
return ;
}
switch ( e . Action )
{
case NotifyCollectionChangedAction . Add :
AddTransitions ( e . NewItems ) ;
break ;
case NotifyCollectionChangedAction . Remove :
RemoveTransitions ( e . OldItems ) ;
break ;
case NotifyCollectionChangedAction . Replace :
RemoveTransitions ( e . OldItems ) ;
AddTransitions ( e . NewItems ) ;
break ;
case NotifyCollectionChangedAction . Reset :
throw new NotSupportedException ( "Transitions collection cannot be reset." ) ;
}
}
protected override void OnPropertyChanged < T > (
AvaloniaProperty < T > property ,
Optional < T > oldValue ,
BindingValue < T > newValue ,
BindingPriority priority )
private void AddTransitions ( IList items )
{
if ( _ transitions is null | | _ previousTransitions is null | | priority = = BindingPriority . Animation )
if ( ! _ transitionsEnabled )
{
return ;
}
_ transitionState ? ? = new Dictionary < ITransition , TransitionState > ( ) ;
// PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations).
foreach ( var transition in _ transitions )
for ( var i = 0 ; i < items . Count ; + + i )
{
if ( transition . Property = = property )
var t = ( ITransition ) items [ i ] ;
_ transitionState . Add ( t , new TransitionState
{
if ( _ previousTransitions . TryGetValue ( property , out var dispose ) )
dispose . Dispose ( ) ;
BaseValue = GetAnimationBaseValue ( t . Property ) ,
} ) ;
}
}
var instance = transition . Apply (
this ,
Clock ? ? Avalonia . Animation . Clock . GlobalClock ,
oldValue . GetValueOrDefault ( ) ,
newValue . GetValueOrDefault ( ) ) ;
private void RemoveTransitions ( IList items )
{
if ( _ transitionState is null )
{
return ;
}
_ previousTransitions [ property ] = instance ;
return ;
for ( var i = 0 ; i < items . Count ; + + i )
{
var t = ( ITransition ) items [ i ] ;
if ( _ transitionState . TryGetValue ( t , out var state ) )
{
state . Instance ? . Dispose ( ) ;
_ transitionState . Remove ( t ) ;
}
}
}
private object GetAnimationBaseValue ( AvaloniaProperty property )
{
var value = this . GetBaseValue ( property , BindingPriority . LocalValue ) ;
if ( value = = AvaloniaProperty . UnsetValue )
{
value = GetValue ( property ) ;
}
return value ;
}
private class TransitionState
{
public IDisposable ? Instance { get ; set ; }
public object? BaseValue { get ; set ; }
}
}
}