From 7f26402bd363552d63f170b4a3a7c7e5a23e8061 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Jun 2023 11:22:24 +0200 Subject: [PATCH 01/11] Set PointerOverRoot. Fixes DevTools not working when pressing Ctrl+Shift (there's a check that `vm.PointerOverRoot` is not null in `MainWindow.xaml.cs`). --- src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs | 1 + 1 file changed, 1 insertion(+) 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); From f912d4ca282cfa448f4f446db71ed099f63f895b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Jun 2023 13:09:03 +0200 Subject: [PATCH 02/11] Improve TreeView.AutoScrollToSelectedItem. It's still of very limited use as the selected item's ancestors need to be manually expanded, but at least when the selected item is realized it should now be scrolled into view. Fixes #10449 --- src/Avalonia.Controls/TreeView.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 7bf8d3bb68..0b22042652 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); @@ -531,6 +536,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); + } } /// From 994fb351d6f291b0a2a66f038d857b956389bd46 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Jun 2023 14:02:11 +0200 Subject: [PATCH 03/11] Fix scroll to selected item logic. - Use `TreeContainerFromItem` when trying to find the container for the selected item, it can be anywhere in the tree - Only scroll to `SelectedItem`, not another item in `SelectedItems` --- src/Avalonia.Controls/TreeView.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 0b22042652..56a5999f29 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -358,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(); } From fb4d231e8424ce304adb72892e4feb711261b5b1 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 2 Jun 2023 14:10:58 +0000 Subject: [PATCH 04/11] fix touch capture target being lost --- src/Avalonia.Base/Input/TouchDevice.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); From 9b51c6b7f5a436df908c9193271ed636c2cd3e5b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Jun 2023 19:55:41 +0600 Subject: [PATCH 05/11] Added TopLevel.RequestAnimationFrame --- src/Avalonia.Base/Media/MediaContext.Clock.cs | 37 +++++++++++++++++-- src/Avalonia.Base/Media/MediaContext.cs | 3 +- src/Avalonia.Controls/TopLevel.cs | 12 +++++- 3 files changed, 45 insertions(+), 7 deletions(-) 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.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); From 14b8025d8346d8e41c5b8f25781bc7386651de9a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Jun 2023 22:31:13 +0600 Subject: [PATCH 06/11] Fixes for animation registry --- .../RenderDemo/Pages/CustomStringAnimator.cs | 5 +- .../Animation/Animation.AnimatorRegistry.cs | 94 +++++++++++++++++++ src/Avalonia.Base/Animation/Animation.cs | 59 +----------- .../Animation/ICustomAnimator.cs | 30 +++++- src/Avalonia.Base/CornerRadius.cs | 7 -- src/Avalonia.Base/Media/BoxShadow.cs | 6 -- src/Avalonia.Base/Media/BoxShadows.cs | 6 -- src/Avalonia.Base/Media/Brush.cs | 5 - src/Avalonia.Base/Media/Color.cs | 7 -- .../Media/Effects/EffectAnimator.cs | 2 - src/Avalonia.Base/Media/Transform.cs | 6 -- src/Avalonia.Base/Point.cs | 7 -- src/Avalonia.Base/Rect.cs | 5 - src/Avalonia.Base/RelativePoint.cs | 7 -- src/Avalonia.Base/Size.cs | 7 -- src/Avalonia.Base/Thickness.cs | 7 -- src/Avalonia.Base/Vector.cs | 7 -- 17 files changed, 127 insertions(+), 140 deletions(-) create mode 100644 src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs 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/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/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/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. /// From 971c848c8f889537ca7be20312aa08e10ac616c0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 4 Jun 2023 13:14:02 +0600 Subject: [PATCH 07/11] Actually invalidate the server visual on render list invalidation --- .../Server/ServerCompositionDrawListVisual.cs | 5 ++++- .../Composition/Server/ServerRenderResource.cs | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 763ec3b5f6..6346b2c176 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); } @@ -55,5 +56,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua { return UiVisual.GetType().ToString(); } + + public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated(); #endif } 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; From 5c24e38490df19331cb3c4142fe64d097f675885 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 4 Jun 2023 12:28:36 -0400 Subject: [PATCH 08/11] Add DataGridColumn Tag property --- src/Avalonia.Controls.DataGrid/DataGridColumn.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) 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. /// From e1d0ba108e94e68041858e70b5c0d3a46ad5a935 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 4 Jun 2023 12:56:44 -0400 Subject: [PATCH 09/11] Fix FlatColorPalette default color values --- .../ColorPalettes/FlatColorPalette.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) 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 From 7c4fdd8d129abe5ab80846e2d74f4d8383a890c3 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 4 Jun 2023 14:58:23 -0400 Subject: [PATCH 10/11] Always coerce empty color in ColorSpectrum --- .../ColorSpectrum/ColorSpectrum.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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; } } From 780c4d7076f03e3728792d78b475da770ea4806b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 5 Jun 2023 01:46:09 +0600 Subject: [PATCH 11/11] Whoops, conditional compilation --- .../Composition/Server/ServerCompositionDrawListVisual.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 6346b2c176..7d6e9442d1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -51,12 +51,12 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua base.RenderCore(canvas, currentTransformedClip); } + public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated(); + #if DEBUG public override string ToString() { return UiVisual.GetType().ToString(); } - - public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated(); #endif }