diff --git a/samples/RenderDemo/Pages/CustomStringAnimator.cs b/samples/RenderDemo/Pages/CustomStringAnimator.cs index 398319726e..4aea131870 100644 --- a/samples/RenderDemo/Pages/CustomStringAnimator.cs +++ b/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 + public class CustomStringAnimator : InterpolatingAnimator { public override string Interpolate(double progress, string oldValue, string newValue) { diff --git a/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs b/src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs new file mode 100644 index 0000000000..6aaafa1b96 --- /dev/null +++ b/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 +{ + /// + /// Sets the value of the Animator attached property for a setter. + /// + /// The animation setter. + /// The property animator value. + [Obsolete("CustomAnimatorBase will be removed before 11.0, use InterpolatingAnimator", true)] + public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value) + { + s_animators[setter] = (value.WrapperType, value.CreateWrapper); + } + + /// + /// Sets the value of the Animator attached property for a setter. + /// + /// The animation setter. + /// The property animator value. + public static void SetAnimator(IAnimationSetter setter, ICustomAnimator value) + { + s_animators[setter] = (value.WrapperType, value.CreateWrapper); + } + + private readonly static List<(Func Condition, Type Animator, Func 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(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + RegisterAnimator(); + } + + /// + /// Registers a that can handle + /// a value type that matches the specified condition. + /// + static void RegisterAnimator() + where TAnimator : Animator, new() + { + Animators.Insert(0, + (prop => typeof(T).IsAssignableFrom(prop.PropertyType), typeof(TAnimator), () => new TAnimator())); + } + + public static void RegisterCustomAnimator() where TAnimator : InterpolatingAnimator, new() + { + Animators.Insert(0, (prop => typeof(T).IsAssignableFrom(prop.PropertyType), + typeof(InterpolatingAnimator.AnimatorWrapper), () => new TAnimator().CreateWrapper())); + } + + private static (Type Type, Func Factory)? GetAnimatorType(AvaloniaProperty property) + { + foreach (var (condition, type, factory) in Animators) + { + if (condition(property)) + { + return (type, factory); + } + } + + return null; + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs index 4de89bfe97..f584cad951 100644 --- a/src/Avalonia.Base/Animation/Animation.cs +++ b/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 /// /// Tracks the progress of an animation. /// - public sealed class Animation : AvaloniaObject, IAnimation + public sealed partial class Animation : AvaloniaObject, IAnimation { /// /// Defines the property. @@ -195,60 +192,6 @@ namespace Avalonia.Animation return null; } - /// - /// Sets the value of the Animator attached property for a setter. - /// - /// The animation setter. - /// The property animator value. - public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value) - { - s_animators[setter] = (value.WrapperType, value.CreateWrapper); - } - - private readonly static List<(Func Condition, Type Animator, Func 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() ), - }; - - /// - /// Registers a that can handle - /// a value type that matches the specified condition. - /// - /// - /// The condition to which the - /// is to be activated and used. - /// - /// - /// The type of the animator to instantiate. - /// - internal static void RegisterAnimator(Func condition) - where TAnimator : IAnimator, new() - { - Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator())); - } - - private static (Type Type, Func Factory)? GetAnimatorType(AvaloniaProperty property) - { - foreach (var (condition, type, factory) in Animators) - { - if (condition(property)) - { - return (type, factory); - } - } - return null; - } - private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control) { var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func>(); diff --git a/src/Avalonia.Base/Animation/ICustomAnimator.cs b/src/Avalonia.Base/Animation/ICustomAnimator.cs index 88c9974ca6..119a6115da 100644 --- a/src/Avalonia.Base/Animation/ICustomAnimator.cs +++ b/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", 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", true)] public abstract class CustomAnimatorBase : CustomAnimatorBase { public abstract T Interpolate(double progress, T oldValue, T newValue); @@ -25,6 +26,33 @@ public abstract class CustomAnimatorBase : 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 : 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 + { + private readonly InterpolatingAnimator _parent; + + public AnimatorWrapper(InterpolatingAnimator parent) + { + _parent = parent; + } + public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue); } } \ No newline at end of file diff --git a/src/Avalonia.Base/CornerRadius.cs b/src/Avalonia.Base/CornerRadius.cs index 82791999d7..44b45c2a73 100644 --- a/src/Avalonia.Base/CornerRadius.cs +++ b/src/Avalonia.Base/CornerRadius.cs @@ -15,13 +15,6 @@ namespace Avalonia #endif readonly struct CornerRadius : IEquatable { - static CornerRadius() - { -#if !BUILDTASK - Animation.Animation.RegisterAnimator(prop => typeof(CornerRadius).IsAssignableFrom(prop.PropertyType)); -#endif - } - public CornerRadius(double uniformRadius) { TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius; diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index 8868e966f0..04c444f441 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -51,7 +51,7 @@ namespace Avalonia.Input pointer.Capture(hit); } - var target = pointer.Captured ?? args.Root; + var target = pointer.Captured ?? args.InputHitTestResult ?? args.Root; var gestureTarget = pointer.CapturedGestureRecognizer?.Target; var updateKind = args.Type.ToUpdateKind(); var keyModifier = args.InputModifiers.ToKeyModifiers(); diff --git a/src/Avalonia.Base/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs index 32b2f7a2fb..91529353ed 100644 --- a/src/Avalonia.Base/Media/BoxShadow.cs +++ b/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(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); diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs index ca16452a96..385f73f703 100644 --- a/src/Avalonia.Base/Media/BoxShadows.cs +++ b/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(prop => - typeof(BoxShadows).IsAssignableFrom(prop.PropertyType)); - } public BoxShadows(BoxShadow shadow) { diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs index 986e3221e6..21be08b4af 100644 --- a/src/Avalonia.Base/Media/Brush.cs +++ b/src/Avalonia.Base/Media/Brush.cs @@ -34,11 +34,6 @@ namespace Avalonia.Media /// public static readonly StyledProperty TransformOriginProperty = AvaloniaProperty.Register(nameof(TransformOrigin)); - - static Brush() - { - Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType)); - } /// /// Gets or sets the opacity of the brush. diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 3ee151389a..17ee14e533 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/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(prop => typeof(Color).IsAssignableFrom(prop.PropertyType)); -#endif - } - /// /// Gets the Alpha component of the color. /// diff --git a/src/Avalonia.Base/Media/Effects/EffectAnimator.cs b/src/Avalonia.Base/Media/Effects/EffectAnimator.cs index 8353afa6ab..cdf6aa6b63 100644 --- a/src/Avalonia.Base/Media/Effects/EffectAnimator.cs +++ b/src/Avalonia.Base/Media/Effects/EffectAnimator.cs @@ -63,8 +63,6 @@ internal class EffectAnimator : Animator if(s_Registered) return; s_Registered = true; - Animation.RegisterAnimator(prop => - typeof(IEffect).IsAssignableFrom(prop.PropertyType)); } } diff --git a/src/Avalonia.Base/Media/MediaContext.Clock.cs b/src/Avalonia.Base/Media/MediaContext.Clock.cs index dc2a39a822..ea8ac13e06 100644 --- a/src/Avalonia.Base/Media/MediaContext.Clock.cs +++ b/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> _observers = new(); - public bool HasNewSubscriptions { get; set; } - public bool HasSubscriptions => _observers.Count > 0; + private List> _newObservers = new(); + private Queue> _queuedAnimationFrames = new(); + private Queue> _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 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 action) => _clock.RequestAnimationFrame(action); } \ No newline at end of file diff --git a/src/Avalonia.Base/Media/MediaContext.cs b/src/Avalonia.Base/Media/MediaContext.cs index 05872a3e50..84a4a8c873 100644 --- a/src/Avalonia.Base/Media/MediaContext.cs +++ b/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; } diff --git a/src/Avalonia.Base/Media/Transform.cs b/src/Avalonia.Base/Media/Transform.cs index 1ac81808a1..16ae137b39 100644 --- a/src/Avalonia.Base/Media/Transform.cs +++ b/src/Avalonia.Base/Media/Transform.cs @@ -15,12 +15,6 @@ namespace Avalonia.Media /// public abstract class Transform : Animatable, IMutableTransform, ICompositionRenderResource, ICompositorSerializable { - static Transform() - { - Animation.Animation.RegisterAnimator(prop => - typeof(ITransform).IsAssignableFrom(prop.OwnerType)); - } - internal Transform() { diff --git a/src/Avalonia.Base/Point.cs b/src/Avalonia.Base/Point.cs index d11596d6be..331cce4a76 100644 --- a/src/Avalonia.Base/Point.cs +++ b/src/Avalonia.Base/Point.cs @@ -16,13 +16,6 @@ namespace Avalonia #endif readonly struct Point : IEquatable { - static Point() - { -#if !BUILDTASK - Animation.Animation.RegisterAnimator(prop => typeof(Point).IsAssignableFrom(prop.PropertyType)); -#endif - } - /// /// The X position. /// diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index d9218ab36e..433f46b66f 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -11,11 +11,6 @@ namespace Avalonia /// public readonly struct Rect : IEquatable { - static Rect() - { - Animation.Animation.RegisterAnimator(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType)); - } - /// /// The X position. /// diff --git a/src/Avalonia.Base/RelativePoint.cs b/src/Avalonia.Base/RelativePoint.cs index 71c6a5cc15..5f04f4d57f 100644 --- a/src/Avalonia.Base/RelativePoint.cs +++ b/src/Avalonia.Base/RelativePoint.cs @@ -54,13 +54,6 @@ namespace Avalonia private readonly RelativeUnit _unit; - static RelativePoint() - { -#if !BUILDTASK - Animation.Animation.RegisterAnimator(prop => typeof(RelativePoint).IsAssignableFrom(prop.PropertyType)); -#endif - } - /// /// Initializes a new instance of the struct. /// diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 763ec3b5f6..7d6e9442d1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -14,7 +14,7 @@ namespace Avalonia.Rendering.Composition.Server; /// /// Server-side counterpart of /// -internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual +internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual, IServerRenderResourceObserver { #if DEBUG // This is needed for debugging purposes so we could see inspect the associated visual from debugger @@ -37,6 +37,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua { _renderCommands?.Dispose(); _renderCommands = reader.ReadObject(); + _renderCommands?.AddObserver(this); } base.DeserializeChangesCore(reader, committedAt); } @@ -50,6 +51,8 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua base.RenderCore(canvas, currentTransformedClip); } + public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated(); + #if DEBUG public override string ToString() { diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs index fad1995092..105580e6ad 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs @@ -13,8 +13,8 @@ internal interface IServerRenderResourceObserver internal interface IServerRenderResource : IServerRenderResourceObserver { - void AddObserver(IServerRenderResource observer); - void RemoveObserver(IServerRenderResource observer); + void AddObserver(IServerRenderResourceObserver observer); + void RemoveObserver(IServerRenderResourceObserver observer); void QueuedInvalidate(); } @@ -23,7 +23,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes private bool _pendingInvalidation; private bool _disposed; public bool IsDisposed => _disposed; - private RefCountingSmallDictionary _observers; + private RefCountingSmallDictionary _observers; public SimpleServerRenderResource(ServerCompositor compositor) : base(compositor) { @@ -97,7 +97,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes } - public void AddObserver(IServerRenderResource observer) + public void AddObserver(IServerRenderResourceObserver observer) { Debug.Assert(!_disposed); if(_disposed) @@ -105,7 +105,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes _observers.Add(observer); } - public void RemoveObserver(IServerRenderResource observer) + public void RemoveObserver(IServerRenderResourceObserver observer) { if (_disposed) return; diff --git a/src/Avalonia.Base/Size.cs b/src/Avalonia.Base/Size.cs index 7781aec607..5ee4541571 100644 --- a/src/Avalonia.Base/Size.cs +++ b/src/Avalonia.Base/Size.cs @@ -15,13 +15,6 @@ namespace Avalonia #endif readonly struct Size : IEquatable { - static Size() - { -#if !BUILDTASK - Animation.Animation.RegisterAnimator(prop => typeof(Size).IsAssignableFrom(prop.PropertyType)); -#endif - } - /// /// A size representing infinity. /// diff --git a/src/Avalonia.Base/Thickness.cs b/src/Avalonia.Base/Thickness.cs index 9513d04782..9673898d09 100644 --- a/src/Avalonia.Base/Thickness.cs +++ b/src/Avalonia.Base/Thickness.cs @@ -15,13 +15,6 @@ namespace Avalonia #endif readonly struct Thickness : IEquatable { - static Thickness() - { -#if !BUILDTASK - Animation.Animation.RegisterAnimator(prop => typeof(Thickness).IsAssignableFrom(prop.PropertyType)); -#endif - } - /// /// The thickness on the left. /// diff --git a/src/Avalonia.Base/Vector.cs b/src/Avalonia.Base/Vector.cs index 085f043627..166ae6b93b 100644 --- a/src/Avalonia.Base/Vector.cs +++ b/src/Avalonia.Base/Vector.cs @@ -17,13 +17,6 @@ namespace Avalonia #endif readonly struct Vector : IEquatable { - static Vector() - { -#if !BUILDTASK - Animation.Animation.RegisterAnimator(prop => typeof(Vector).IsAssignableFrom(prop.PropertyType)); -#endif - } - /// /// The X component. /// diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs index aaf272c6d2..4700081e6b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs @@ -266,26 +266,26 @@ namespace Avalonia.Controls MidnightBlue9 = 0xFF1C2833, MidnightBlue10 = 0xFF17202A, - Pomegranate = Pomegranate3, - Alizarin = Alizarin3, - Amethyst = Amethyst3, - Wisteria = Wisteria3, - BelizeHole = BelizeHole3, - PeterRiver = PeterRiver3, - Turquoise = Turquoise3, - GreenSea = GreenSea3, - Nephritis = Nephritis3, - Emerald = Emerald3, - Sunflower = Sunflower3, - Orange = Orange3, - Carrot = Carrot3, - Pumpkin = Pumpkin3, - Clouds = Clouds3, - Silver = Silver3, - Concrete = Concrete3, - Asbestos = Asbestos3, - WetAsphalt = WetAsphalt3, - MidnightBlue = MidnightBlue3, + Pomegranate = Pomegranate6, + Alizarin = Alizarin6, + Amethyst = Amethyst6, + Wisteria = Wisteria6, + BelizeHole = BelizeHole6, + PeterRiver = PeterRiver6, + Turquoise = Turquoise6, + GreenSea = GreenSea6, + Nephritis = Nephritis6, + Emerald = Emerald6, + Sunflower = Sunflower6, + Orange = Orange6, + Carrot = Carrot6, + Pumpkin = Pumpkin6, + Clouds = Clouds6, + Silver = Silver6, + Concrete = Concrete6, + Asbestos = Asbestos6, + WetAsphalt = WetAsphalt6, + MidnightBlue = MidnightBlue6, }; // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 6683346eeb..deb0dfb6dd 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -45,7 +45,6 @@ namespace Avalonia.Controls.Primitives private bool _updatingColor = false; private bool _updatingHsvColor = false; - private bool _coercedInitialColor = false; private bool _isPointerPressed = false; private bool _shouldShowLargeSelection = false; private List _hsvValues = new List(); @@ -622,7 +621,7 @@ namespace Avalonia.Controls.Primitives // that no color has been selected by the user. Note that #00000000 is different than // #00FFFFFF (Transparent). // - // In this situation, the first time the user clicks on the spectrum the third + // In this situation, whenever the user clicks on the spectrum, the third // component and alpha component will remain zero. This is because the spectrum only // controls two components at any given time. // @@ -633,16 +632,19 @@ namespace Avalonia.Controls.Primitives // though the desired value is simply full color. // // To work around this usability issue with an initial #00000000 color, the selected - // color is coerced (only the first time) into a color with maximum third component - // value and maximum alpha. This can only happen once and only if those two components - // are already zero. + // color is coerced into a color with maximum third component value and maximum alpha. + // This can only happen here in the spectrum if those two components are already zero. + // + // In the past this coercion was restricted to occur only one time. However, when + // ColorPicker controls are re-used or recycled #00000000 can be set multiple times. + // Each time needs this special logic for usability so now anytime the color is + // changed on the spectrum this logic will run. // // Also note this is NOT currently done for #00FFFFFF (Transparent) but based on // further usability study that case may need to be handled here as well. Right now // Transparent is treated as a normal color value with the alpha intentionally set // to zero so the alpha slider must still be adjusted after the spectrum. - if (!_coercedInitialColor && - IsLoaded) + if (IsLoaded) { bool isAlphaComponentZero = (alpha == 0.0); bool isThirdComponentZero = false; @@ -691,8 +693,6 @@ namespace Avalonia.Controls.Primitives newHsv.H = 360.0; break; } - - _coercedInitialColor = true; } } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 96d072260c..d28c0969c4 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -1103,6 +1103,16 @@ namespace Avalonia.Controls get; set; } + + /// + /// Gets or sets an object associated with this column. + /// + public object Tag + { + get; + set; + } + /// /// Holds a Comparer to use for sorting, if not using the default. /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index a3f50876e0..04a5a0e6aa 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/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(() => { }); } } - + + /// + /// Enqueues a callback to be called on the next animation tick + /// + public void RequestAnimationFrame(Action action) + { + Dispatcher.UIThread.VerifyAccess(); + MediaContext.Instance.RequestAnimationFrame(action); + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 7bf8d3bb68..56a5999f29 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -84,6 +84,11 @@ namespace Avalonia.Controls /// /// Gets or sets a value indicating whether to automatically scroll to newly selected items. /// + /// + /// This property is of limited use with as it will only scroll + /// to realized items. To scroll to a non-expanded item, you need to ensure that its + /// ancestors are expanded. + /// public bool AutoScrollToSelectedItem { get => GetValue(AutoScrollToSelectedItemProperty); @@ -353,9 +358,13 @@ namespace Avalonia.Controls SelectedItemsAdded(e.NewItems!.Cast().ToArray()); - if (AutoScrollToSelectedItem) + var selectedItem = SelectedItem; + + if (AutoScrollToSelectedItem && + selectedItem is not null && + e.NewItems![0] == selectedItem) { - var container = ContainerFromItem(e.NewItems![0]!); + var container = TreeContainerFromItem(selectedItem); container?.BringIntoView(); } @@ -531,6 +540,12 @@ namespace Avalonia.Controls // The IsSelected property is not set on the container: update the container // selection based on the current selection as understood by this control. MarkContainerSelected(container, SelectedItems.Contains(item)); + + // If the newly realized container is the selected container, scroll to it after layout. + if (AutoScrollToSelectedItem && SelectedItem == item) + { + Dispatcher.UIThread.Post(container.BringIntoView, DispatcherPriority.Loaded); + } } /// diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 3adad38ac6..f993d2c957 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -44,6 +44,7 @@ namespace Avalonia.Diagnostics.ViewModels SelectedTab = 0; if (root is TopLevel topLevel) { + _pointerOverRoot = topLevel; _pointerOverSubscription = topLevel.GetObservable(TopLevel.PointerOverElementProperty) .Subscribe(x => PointerOverElement = x);