Browse Source

Merge branch 'fixes/server-visual-invalidate' of github.com:AvaloniaUI/Avalonia into fixes/server-visual-invalidate

pull/11642/head
Nikita Tsukanov 3 years ago
parent
commit
69f171c276
  1. 5
      samples/RenderDemo/Pages/CustomStringAnimator.cs
  2. 94
      src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs
  3. 59
      src/Avalonia.Base/Animation/Animation.cs
  4. 30
      src/Avalonia.Base/Animation/ICustomAnimator.cs
  5. 7
      src/Avalonia.Base/CornerRadius.cs
  6. 6
      src/Avalonia.Base/Media/BoxShadow.cs
  7. 6
      src/Avalonia.Base/Media/BoxShadows.cs
  8. 5
      src/Avalonia.Base/Media/Brush.cs
  9. 7
      src/Avalonia.Base/Media/Color.cs
  10. 2
      src/Avalonia.Base/Media/Effects/EffectAnimator.cs
  11. 37
      src/Avalonia.Base/Media/MediaContext.Clock.cs
  12. 3
      src/Avalonia.Base/Media/MediaContext.cs
  13. 6
      src/Avalonia.Base/Media/Transform.cs
  14. 7
      src/Avalonia.Base/Point.cs
  15. 5
      src/Avalonia.Base/Rect.cs
  16. 7
      src/Avalonia.Base/RelativePoint.cs
  17. 7
      src/Avalonia.Base/Size.cs
  18. 7
      src/Avalonia.Base/Thickness.cs
  19. 7
      src/Avalonia.Base/Vector.cs
  20. 12
      src/Avalonia.Controls/TopLevel.cs

5
samples/RenderDemo/Pages/CustomStringAnimator.cs

@ -1,9 +1,10 @@
using Avalonia.Animation;
using System;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
namespace RenderDemo.Pages
{
public class CustomStringAnimator : CustomAnimatorBase<string>
public class CustomStringAnimator : InterpolatingAnimator<string>
{
public override string Interpolate(double progress, string oldValue, string newValue)
{

94
src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using Avalonia.Animation.Animators;
using Avalonia.Media;
namespace Avalonia.Animation;
partial class Animation
{
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
[Obsolete("CustomAnimatorBase will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
{
s_animators[setter] = (value.WrapperType, value.CreateWrapper);
}
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
public static void SetAnimator(IAnimationSetter setter, ICustomAnimator value)
{
s_animators[setter] = (value.WrapperType, value.CreateWrapper);
}
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator, Func<IAnimator> Factory)>
Animators = new()
{
(prop =>(typeof(double).IsAssignableFrom(prop.PropertyType) && typeof(Transform).IsAssignableFrom(prop.OwnerType)),
typeof(TransformAnimator), () => new TransformAnimator()),
(prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator()),
(prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator()),
(prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator()),
(prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator()),
(prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator()),
(prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator()),
(prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator()),
(prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator()),
(prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator()),
(prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator()),
(prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator()),
};
static Animation()
{
RegisterAnimator<IEffect?, EffectAnimator>();
RegisterAnimator<BoxShadow, BoxShadowAnimator>();
RegisterAnimator<BoxShadows, BoxShadowsAnimator>();
RegisterAnimator<IBrush?, BaseBrushAnimator>();
RegisterAnimator<CornerRadius, CornerRadiusAnimator>();
RegisterAnimator<Color, ColorAnimator>();
RegisterAnimator<Vector, VectorAnimator>();
RegisterAnimator<Point, PointAnimator>();
RegisterAnimator<Rect, RectAnimator>();
RegisterAnimator<RelativePoint, RelativePointAnimator>();
RegisterAnimator<Size, SizeAnimator>();
RegisterAnimator<Thickness, ThicknessAnimator>();
}
/// <summary>
/// Registers a <see cref="Animator{T}"/> that can handle
/// a value type that matches the specified condition.
/// </summary>
static void RegisterAnimator<T, TAnimator>()
where TAnimator : Animator<T>, new()
{
Animators.Insert(0,
(prop => typeof(T).IsAssignableFrom(prop.PropertyType), typeof(TAnimator), () => new TAnimator()));
}
public static void RegisterCustomAnimator<T, TAnimator>() where TAnimator : InterpolatingAnimator<T>, new()
{
Animators.Insert(0, (prop => typeof(T).IsAssignableFrom(prop.PropertyType),
typeof(InterpolatingAnimator<T>.AnimatorWrapper), () => new TAnimator().CreateWrapper()));
}
private static (Type Type, Func<IAnimator> Factory)? GetAnimatorType(AvaloniaProperty property)
{
foreach (var (condition, type, factory) in Animators)
{
if (condition(property))
{
return (type, factory);
}
}
return null;
}
}

59
src/Avalonia.Base/Animation/Animation.cs

@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Reactive;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Data;
using Avalonia.Metadata;
@ -16,7 +13,7 @@ namespace Avalonia.Animation
/// <summary>
/// Tracks the progress of an animation.
/// </summary>
public sealed class Animation : AvaloniaObject, IAnimation
public sealed partial class Animation : AvaloniaObject, IAnimation
{
/// <summary>
/// Defines the <see cref="Duration"/> property.
@ -195,60 +192,6 @@ namespace Avalonia.Animation
return null;
}
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
{
s_animators[setter] = (value.WrapperType, value.CreateWrapper);
}
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator, Func<IAnimator> Factory)> Animators = new()
{
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator() ),
( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator() ),
( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator() ),
( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator() ),
( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator() ),
( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator() ),
( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator() ),
( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator() ),
( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator() ),
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator() ),
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator() ),
};
/// <summary>
/// Registers a <see cref="Animator{T}"/> that can handle
/// a value type that matches the specified condition.
/// </summary>
/// <param name="condition">
/// The condition to which the <see cref="Animator{T}"/>
/// is to be activated and used.
/// </param>
/// <typeparam name="TAnimator">
/// The type of the animator to instantiate.
/// </typeparam>
internal static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator : IAnimator, new()
{
Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
}
private static (Type Type, Func<IAnimator> Factory)? GetAnimatorType(AvaloniaProperty property)
{
foreach (var (condition, type, factory) in Animators)
{
if (condition(property))
{
return (type, factory);
}
}
return null;
}
private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
{
var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func<IAnimator>>();

30
src/Avalonia.Base/Animation/ICustomAnimator.cs

@ -1,14 +1,15 @@
using System;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation;
[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public abstract class CustomAnimatorBase
{
internal abstract IAnimator CreateWrapper();
internal abstract Type WrapperType { get; }
}
[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public abstract class CustomAnimatorBase<T> : CustomAnimatorBase
{
public abstract T Interpolate(double progress, T oldValue, T newValue);
@ -25,6 +26,33 @@ public abstract class CustomAnimatorBase<T> : CustomAnimatorBase
_parent = parent;
}
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
}
}
public interface ICustomAnimator
{
internal IAnimator CreateWrapper();
internal Type WrapperType { get; }
}
public abstract class InterpolatingAnimator<T> : ICustomAnimator
{
public abstract T Interpolate(double progress, T oldValue, T newValue);
Type ICustomAnimator.WrapperType => typeof(AnimatorWrapper);
IAnimator ICustomAnimator.CreateWrapper() => new AnimatorWrapper(this);
internal IAnimator CreateWrapper() => new AnimatorWrapper(this);
internal class AnimatorWrapper : Animator<T>
{
private readonly InterpolatingAnimator<T> _parent;
public AnimatorWrapper(InterpolatingAnimator<T> parent)
{
_parent = parent;
}
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
}
}

7
src/Avalonia.Base/CornerRadius.cs

@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct CornerRadius : IEquatable<CornerRadius>
{
static CornerRadius()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<CornerRadiusAnimator>(prop => typeof(CornerRadius).IsAssignableFrom(prop.PropertyType));
#endif
}
public CornerRadius(double uniformRadius)
{
TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius;

6
src/Avalonia.Base/Media/BoxShadow.cs

@ -16,12 +16,6 @@ namespace Avalonia.Media
public Color Color { get; set; }
public bool IsInset { get; set; }
static BoxShadow()
{
Animation.Animation.RegisterAnimator<BoxShadowAnimator>(prop =>
typeof(BoxShadow).IsAssignableFrom(prop.PropertyType));
}
public bool Equals(in BoxShadow other)
{
return OffsetX.Equals(other.OffsetX) && OffsetY.Equals(other.OffsetY) && Blur.Equals(other.Blur) && Spread.Equals(other.Spread) && Color.Equals(other.Color);

6
src/Avalonia.Base/Media/BoxShadows.cs

@ -10,12 +10,6 @@ namespace Avalonia.Media
private readonly BoxShadow _first;
private readonly BoxShadow[]? _list;
public int Count { get; }
static BoxShadows()
{
Animation.Animation.RegisterAnimator<BoxShadowsAnimator>(prop =>
typeof(BoxShadows).IsAssignableFrom(prop.PropertyType));
}
public BoxShadows(BoxShadow shadow)
{

5
src/Avalonia.Base/Media/Brush.cs

@ -34,11 +34,6 @@ namespace Avalonia.Media
/// </summary>
public static readonly StyledProperty<RelativePoint> TransformOriginProperty =
AvaloniaProperty.Register<Brush, RelativePoint>(nameof(TransformOrigin));
static Brush()
{
Animation.Animation.RegisterAnimator<BaseBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// Gets or sets the opacity of the brush.

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

@ -25,13 +25,6 @@ namespace Avalonia.Media
{
private const double byteToDouble = 1.0 / 255;
static Color()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<ColorAnimator>(prop => typeof(Color).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// Gets the Alpha component of the color.
/// </summary>

2
src/Avalonia.Base/Media/Effects/EffectAnimator.cs

@ -63,8 +63,6 @@ internal class EffectAnimator : Animator<IEffect?>
if(s_Registered)
return;
s_Registered = true;
Animation.RegisterAnimator<EffectAnimator>(prop =>
typeof(IEffect).IsAssignableFrom(prop.PropertyType));
}
}

37
src/Avalonia.Base/Media/MediaContext.Clock.cs

@ -4,6 +4,7 @@ using System.Diagnostics;
using Avalonia.Animation;
using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Media;
@ -17,8 +18,12 @@ internal partial class MediaContext
{
private readonly MediaContext _parent;
private List<IObserver<TimeSpan>> _observers = new();
public bool HasNewSubscriptions { get; set; }
public bool HasSubscriptions => _observers.Count > 0;
private List<IObserver<TimeSpan>> _newObservers = new();
private Queue<Action<TimeSpan>> _queuedAnimationFrames = new();
private Queue<Action<TimeSpan>> _queuedAnimationFramesNext = new();
private TimeSpan _currentAnimationTimestamp;
public bool HasNewSubscriptions => _newObservers.Count > 0;
public bool HasSubscriptions => _observers.Count > 0 || _queuedAnimationFrames.Count > 0;
public MediaContextClock(MediaContext parent)
{
@ -29,19 +34,41 @@ internal partial class MediaContext
{
_parent.ScheduleRender(false);
Dispatcher.UIThread.VerifyAccess();
HasNewSubscriptions = true;
_observers.Add(observer);
_newObservers.Add(observer);
return Disposable.Create(() =>
{
Dispatcher.UIThread.VerifyAccess();
_observers.Remove(observer);
});
}
public void RequestAnimationFrame(Action<TimeSpan> action)
{
_parent.ScheduleRender(false);
_queuedAnimationFrames.Enqueue(action);
}
public void Pulse(TimeSpan now)
{
_newObservers.Clear();
_currentAnimationTimestamp = now;
// We are swapping the queues before enumeration
(_queuedAnimationFrames, _queuedAnimationFramesNext) = (_queuedAnimationFramesNext, _queuedAnimationFrames);
var animationFrames = _queuedAnimationFramesNext;
while (animationFrames.TryDequeue(out var callback))
callback(now);
foreach (var observer in _observers.ToArray())
observer.OnNext(now);
observer.OnNext(_currentAnimationTimestamp);
}
public void PulseNewSubscriptions()
{
foreach (var observer in _newObservers.ToArray())
observer.OnNext(_currentAnimationTimestamp);
_newObservers.Clear();
}
public PlayState PlayState
@ -50,4 +77,6 @@ internal partial class MediaContext
set => throw new InvalidOperationException();
}
}
public void RequestAnimationFrame(Action<TimeSpan> action) => _clock.RequestAnimationFrame(action);
}

3
src/Avalonia.Base/Media/MediaContext.cs

@ -131,12 +131,11 @@ internal partial class MediaContext : ICompositorScheduler
// We are doing several iterations when it happens
for (var c = 0; c < 10; c++)
{
_clock.HasNewSubscriptions = false;
FireInvokeOnRenderCallbacks();
if (_clock.HasNewSubscriptions)
{
_clock.Pulse(now);
_clock.PulseNewSubscriptions();
continue;
}

6
src/Avalonia.Base/Media/Transform.cs

@ -15,12 +15,6 @@ namespace Avalonia.Media
/// </summary>
public abstract class Transform : Animatable, IMutableTransform, ICompositionRenderResource<ITransform>, ICompositorSerializable
{
static Transform()
{
Animation.Animation.RegisterAnimator<TransformAnimator>(prop =>
typeof(ITransform).IsAssignableFrom(prop.OwnerType));
}
internal Transform()
{

7
src/Avalonia.Base/Point.cs

@ -16,13 +16,6 @@ namespace Avalonia
#endif
readonly struct Point : IEquatable<Point>
{
static Point()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<PointAnimator>(prop => typeof(Point).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// The X position.
/// </summary>

5
src/Avalonia.Base/Rect.cs

@ -11,11 +11,6 @@ namespace Avalonia
/// </summary>
public readonly struct Rect : IEquatable<Rect>
{
static Rect()
{
Animation.Animation.RegisterAnimator<RectAnimator>(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// The X position.
/// </summary>

7
src/Avalonia.Base/RelativePoint.cs

@ -54,13 +54,6 @@ namespace Avalonia
private readonly RelativeUnit _unit;
static RelativePoint()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<RelativePointAnimator>(prop => typeof(RelativePoint).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// Initializes a new instance of the <see cref="RelativePoint"/> struct.
/// </summary>

7
src/Avalonia.Base/Size.cs

@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct Size : IEquatable<Size>
{
static Size()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<SizeAnimator>(prop => typeof(Size).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// A size representing infinity.
/// </summary>

7
src/Avalonia.Base/Thickness.cs

@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct Thickness : IEquatable<Thickness>
{
static Thickness()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<ThicknessAnimator>(prop => typeof(Thickness).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// The thickness on the left.
/// </summary>

7
src/Avalonia.Base/Vector.cs

@ -17,13 +17,6 @@ namespace Avalonia
#endif
readonly struct Vector : IEquatable<Vector>
{
static Vector()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<VectorAnimator>(prop => typeof(Vector).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// The X component.
/// </summary>

12
src/Avalonia.Controls/TopLevel.cs

@ -25,6 +25,7 @@ using System.Linq;
using System.Threading.Tasks;
using Avalonia.Metadata;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.Controls
{
@ -535,7 +536,16 @@ namespace Avalonia.Controls
return Disposable.Create(() => { });
}
}
/// <summary>
/// Enqueues a callback to be called on the next animation tick
/// </summary>
public void RequestAnimationFrame(Action<TimeSpan> action)
{
Dispatcher.UIThread.VerifyAccess();
MediaContext.Instance.RequestAnimationFrame(action);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

Loading…
Cancel
Save