From 3c7c9b458a4a57f9a7a50684056aef91431f275a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 22 Jan 2023 14:18:07 -0800 Subject: [PATCH 1/4] Update ObservableStreamPlugin.cs --- src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index 2b9da0a61a..9cf25281f2 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs @@ -15,7 +15,7 @@ namespace Avalonia.Data.Core.Plugins private static MethodInfo? s_observableGeneric; private static MethodInfo? s_observableSelect; - [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicProperties, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] public ObservableStreamPlugin() { From adefd574b69ba0e984e3b57547999c4277482f88 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Jan 2023 11:25:16 +0100 Subject: [PATCH 2/4] Devirtualize AvaloniaProperty properties. For performance reasons. --- src/Avalonia.Base/AttachedProperty.cs | 4 +--- src/Avalonia.Base/AvaloniaProperty.cs | 8 ++++---- src/Avalonia.Base/DirectProperty.cs | 13 ++++--------- src/Avalonia.Base/DirectPropertyBase.cs | 6 +++--- src/Avalonia.Base/StyledPropertyBase.cs | 17 ++--------------- 5 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index a43194153c..31b6cad8ab 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -24,11 +24,9 @@ namespace Avalonia Func? validate = null) : base(name, ownerType, metadata, inherits, validate) { + IsAttached = true; } - /// - public override bool IsAttached => true; - /// /// Attaches the property as a non-attached property on the specified type. /// diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index e0782c51a2..5db4d81f03 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -107,22 +107,22 @@ namespace Avalonia /// /// Gets a value indicating whether the property inherits its value. /// - public virtual bool Inherits => false; + public bool Inherits { get; private protected set; } /// /// Gets a value indicating whether this is an attached property. /// - public virtual bool IsAttached => false; + public bool IsAttached { get; private protected set; } /// /// Gets a value indicating whether this is a direct property. /// - public virtual bool IsDirect => false; + public bool IsDirect { get; private protected set; } /// /// Gets a value indicating whether this is a readonly property. /// - public virtual bool IsReadOnly => false; + public bool IsReadOnly { get; private protected set; } /// /// Gets an observable that is fired when this property changes on any diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index 729240e5a1..d02e277074 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -33,6 +33,8 @@ namespace Avalonia { Getter = getter ?? throw new ArgumentNullException(nameof(getter)); Setter = setter; + IsDirect = true; + IsReadOnly = setter is null; } /// @@ -51,17 +53,10 @@ namespace Avalonia { Getter = getter ?? throw new ArgumentNullException(nameof(getter)); Setter = setter; + IsDirect = true; + IsReadOnly = setter is null; } - /// - public override bool IsDirect => true; - - /// - public override bool IsReadOnly => Setter == null; - - /// - public override Type Owner => typeof(TOwner); - /// /// Gets the getter function. /// diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index ec9eba6d61..9ee1eee0fa 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -1,8 +1,6 @@ using System; using Avalonia.Data; using Avalonia.PropertyStore; -using Avalonia.Reactive; -using Avalonia.Styling; namespace Avalonia { @@ -28,6 +26,7 @@ namespace Avalonia AvaloniaPropertyMetadata metadata) : base(name, ownerType, metadata) { + Owner = ownerType; } /// @@ -42,12 +41,13 @@ namespace Avalonia AvaloniaPropertyMetadata metadata) : base(source, ownerType, metadata) { + Owner = ownerType; } /// /// Gets the type that registered the property. /// - public abstract Type Owner { get; } + public Type Owner { get; } /// /// Gets the value of the property on the instance. diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index a281a7b7f6..b39f45189c 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -1,10 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reflection; using Avalonia.Data; using Avalonia.PropertyStore; -using Avalonia.Reactive; -using Avalonia.Styling; using Avalonia.Utilities; namespace Avalonia @@ -14,8 +11,6 @@ namespace Avalonia /// public abstract class StyledPropertyBase : AvaloniaProperty, IStyledPropertyAccessor { - private readonly bool _inherits; - /// /// Initializes a new instance of the class. /// @@ -34,7 +29,7 @@ namespace Avalonia Action? notifying = null) : base(name, ownerType, metadata, notifying) { - _inherits = inherits; + Inherits = inherits; ValidateValue = validate; HasCoercion |= metadata.CoerceValue != null; @@ -53,17 +48,9 @@ namespace Avalonia protected StyledPropertyBase(StyledPropertyBase source, Type ownerType) : base(source, ownerType, null) { - _inherits = source.Inherits; + Inherits = source.Inherits; } - /// - /// Gets a value indicating whether the property inherits its value. - /// - /// - /// A value indicating whether the property inherits its value. - /// - public override bool Inherits => _inherits; - /// /// Gets the value validation callback for the property. /// From 17c3291c80acbfe7e112a366eb30a69924adea9a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 23 Jan 2023 12:06:40 +0100 Subject: [PATCH 3/4] Remove StyledPropertyBase class. Originally `StyledPropertyBase` was the base class for `StyledProperty` and `AttachedProperty` however #1499 made `AttachedProperty` derive directly from `StyledProperty` meaning that there is no longer any need for a separate `StyledPropertyBase` class. --- src/Avalonia.Base/AvaloniaObject.cs | 16 +- src/Avalonia.Base/AvaloniaObjectExtensions.cs | 8 +- .../PropertyStore/EffectiveValue`1.cs | 20 +- .../PropertyStore/ImmediateValueEntry.cs | 4 +- .../PropertyStore/ImmediateValueFrame.cs | 8 +- .../LocalValueBindingObserver.cs | 6 +- .../LocalValueUntypedBindingObserver.cs | 4 +- .../SourceUntypedBindingEntry.cs | 4 +- .../PropertyStore/TypedBindingEntry.cs | 6 +- .../PropertyStore/UntypedValueUtils.cs | 2 +- src/Avalonia.Base/PropertyStore/ValueStore.cs | 18 +- src/Avalonia.Base/StyledProperty.cs | 208 ++++++++++++++- src/Avalonia.Base/StyledPropertyBase.cs | 237 ------------------ .../Styling/PropertySetterInstance.cs | 4 +- .../AvaloniaXamlIlWellKnownTypes.cs | 2 +- 15 files changed, 248 insertions(+), 299 deletions(-) delete mode 100644 src/Avalonia.Base/StyledPropertyBase.cs diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index dc94dfba40..1946d4ba5c 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -132,7 +132,7 @@ namespace Avalonia switch (property) { - case StyledPropertyBase styled: + case StyledProperty styled: ClearValue(styled); break; case DirectPropertyBase direct: @@ -147,7 +147,7 @@ namespace Avalonia /// Clears a 's local value. /// /// The property. - public void ClearValue(StyledPropertyBase property) + public void ClearValue(StyledProperty property) { property = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); @@ -220,7 +220,7 @@ namespace Avalonia /// The type of the property. /// The property. /// The value. - public T GetValue(StyledPropertyBase property) + public T GetValue(StyledProperty property) { _ = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); @@ -243,7 +243,7 @@ namespace Avalonia } /// - public Optional GetBaseValue(StyledPropertyBase property) + public Optional GetBaseValue(StyledProperty property) { _ = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); @@ -309,7 +309,7 @@ namespace Avalonia /// An if setting the property can be undone, otherwise null. /// public IDisposable? SetValue( - StyledPropertyBase property, + StyledProperty property, T value, BindingPriority priority = BindingPriority.LocalValue) { @@ -373,7 +373,7 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public IDisposable Bind( - StyledPropertyBase property, + StyledProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) { @@ -396,7 +396,7 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public IDisposable Bind( - StyledPropertyBase property, + StyledProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) { @@ -419,7 +419,7 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public IDisposable Bind( - StyledPropertyBase property, + StyledProperty property, IObservable> source, BindingPriority priority = BindingPriority.LocalValue) { diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 7b17b9152d..6231483ff8 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -146,7 +146,7 @@ namespace Avalonia return property switch { - StyledPropertyBase styled => target.Bind(styled, source, priority), + StyledProperty styled => target.Bind(styled, source, priority), DirectPropertyBase direct => target.Bind(direct, source), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), }; @@ -170,7 +170,7 @@ namespace Avalonia { return property switch { - StyledPropertyBase styled => target.Bind(styled, source, priority), + StyledProperty styled => target.Bind(styled, source, priority), DirectPropertyBase direct => target.Bind(direct, source), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), }; @@ -231,7 +231,7 @@ namespace Avalonia return property switch { - StyledPropertyBase styled => target.GetValue(styled), + StyledProperty styled => target.GetValue(styled), DirectPropertyBase direct => target.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; @@ -280,7 +280,7 @@ namespace Avalonia return property switch { - StyledPropertyBase styled => target.GetBaseValue(styled), + StyledProperty styled => target.GetBaseValue(styled), DirectPropertyBase direct => target.GetValue(direct), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") }; diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 93fffb3755..3e20dcce56 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -19,7 +19,7 @@ namespace Avalonia.PropertyStore private T? _baseValue; private UncommonFields? _uncommon; - public EffectiveValue(AvaloniaObject owner, StyledPropertyBase property) + public EffectiveValue(AvaloniaObject owner, StyledProperty property) { Priority = BindingPriority.Unset; BasePriority = BindingPriority.Unset; @@ -57,12 +57,12 @@ namespace Avalonia.PropertyStore Debug.Assert(priority != BindingPriority.LocalValue); UpdateValueEntry(value, priority); - SetAndRaiseCore(owner, (StyledPropertyBase)value.Property, GetValue(value), priority); + SetAndRaiseCore(owner, (StyledProperty)value.Property, GetValue(value), priority); } public void SetLocalValueAndRaise( ValueStore owner, - StyledPropertyBase property, + StyledProperty property, T value) { SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue); @@ -82,7 +82,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(oldValue is not null || newValue is not null); - var p = (StyledPropertyBase)property; + var p = (StyledProperty)property; var o = oldValue is not null ? ((EffectiveValue)oldValue).Value : _metadata.DefaultValue; var n = newValue is not null ? ((EffectiveValue)newValue).Value : _metadata.DefaultValue; var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset; @@ -98,7 +98,7 @@ namespace Avalonia.PropertyStore Debug.Assert(Priority != BindingPriority.Animation); Debug.Assert(BasePriority != BindingPriority.Unset); UpdateValueEntry(null, BindingPriority.Animation); - SetAndRaiseCore(owner, (StyledPropertyBase)property, _baseValue!, BasePriority); + SetAndRaiseCore(owner, (StyledProperty)property, _baseValue!, BasePriority); } public override void CoerceValue(ValueStore owner, AvaloniaProperty property) @@ -107,7 +107,7 @@ namespace Avalonia.PropertyStore return; SetAndRaiseCore( owner, - (StyledPropertyBase)property, + (StyledProperty)property, _uncommon._uncoercedValue!, Priority, _uncommon._uncoercedBaseValue!, @@ -117,10 +117,10 @@ namespace Avalonia.PropertyStore public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property) { UnsubscribeValueEntries(); - DisposeAndRaiseUnset(owner, (StyledPropertyBase)property); + DisposeAndRaiseUnset(owner, (StyledProperty)property); } - public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase property) + public void DisposeAndRaiseUnset(ValueStore owner, StyledProperty property) { BindingPriority priority; T oldValue; @@ -156,7 +156,7 @@ namespace Avalonia.PropertyStore private void SetAndRaiseCore( ValueStore owner, - StyledPropertyBase property, + StyledProperty property, T value, BindingPriority priority) { @@ -203,7 +203,7 @@ namespace Avalonia.PropertyStore private void SetAndRaiseCore( ValueStore owner, - StyledPropertyBase property, + StyledProperty property, T value, BindingPriority priority, T baseValue, diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs index 364b4e1225..d8a353dc70 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs @@ -9,7 +9,7 @@ namespace Avalonia.PropertyStore public ImmediateValueEntry( ImmediateValueFrame owner, - StyledPropertyBase property, + StyledProperty property, T value) { _owner = owner; @@ -17,7 +17,7 @@ namespace Avalonia.PropertyStore Property = property; } - public StyledPropertyBase Property { get; } + public StyledProperty Property { get; } public bool HasValue => true; AvaloniaProperty IValueEntry.Property => Property; diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs index 50d5333b9f..7e9f3ab312 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs @@ -15,7 +15,7 @@ namespace Avalonia.PropertyStore } public TypedBindingEntry AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable> source) { var e = new TypedBindingEntry(this, property, source); @@ -24,7 +24,7 @@ namespace Avalonia.PropertyStore } public TypedBindingEntry AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable source) { var e = new TypedBindingEntry(this, property, source); @@ -33,7 +33,7 @@ namespace Avalonia.PropertyStore } public SourceUntypedBindingEntry AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable source) { var e = new SourceUntypedBindingEntry(this, property, source); @@ -41,7 +41,7 @@ namespace Avalonia.PropertyStore return e; } - public ImmediateValueEntry AddValue(StyledPropertyBase property, T value) + public ImmediateValueEntry AddValue(StyledProperty property, T value) { var e = new ImmediateValueEntry(this, property, value); Add(e); diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index 8acb885604..f89cb029b6 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -11,13 +11,13 @@ namespace Avalonia.PropertyStore private readonly ValueStore _owner; private IDisposable? _subscription; - public LocalValueBindingObserver(ValueStore owner, StyledPropertyBase property) + public LocalValueBindingObserver(ValueStore owner, StyledProperty property) { _owner = owner; Property = property; } - public StyledPropertyBase Property { get;} + public StyledProperty Property { get;} public void Start(IObservable source) { @@ -41,7 +41,7 @@ namespace Avalonia.PropertyStore public void OnNext(T value) { - static void Execute(ValueStore owner, StyledPropertyBase property, T value) + static void Execute(ValueStore owner, StyledProperty property, T value) { if (property.ValidateValue?.Invoke(value) != false) owner.SetValue(property, value, BindingPriority.LocalValue); diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs index 7c529591b6..2d157b2519 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs @@ -11,13 +11,13 @@ namespace Avalonia.PropertyStore private readonly ValueStore _owner; private IDisposable? _subscription; - public LocalValueUntypedBindingObserver(ValueStore owner, StyledPropertyBase property) + public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty property) { _owner = owner; Property = property; } - public StyledPropertyBase Property { get; } + public StyledProperty Property { get; } public void Start(IObservable source) { diff --git a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs index b4ac06d2bf..b56d0d4529 100644 --- a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs @@ -13,14 +13,14 @@ namespace Avalonia.PropertyStore public SourceUntypedBindingEntry( ValueFrame frame, - StyledPropertyBase property, + StyledProperty property, IObservable source) : base(frame, property, source) { _validate = property.ValidateValue; } - public new StyledPropertyBase Property => (StyledPropertyBase)base.Property; + public new StyledProperty Property => (StyledProperty)base.Property; protected override BindingValue ConvertAndValidate(object? value) { diff --git a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs index 2276991a18..697725c87b 100644 --- a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs @@ -11,7 +11,7 @@ namespace Avalonia.PropertyStore { public TypedBindingEntry( ValueFrame frame, - StyledPropertyBase property, + StyledProperty property, IObservable source) : base(frame, property, source) { @@ -19,13 +19,13 @@ namespace Avalonia.PropertyStore public TypedBindingEntry( ValueFrame frame, - StyledPropertyBase property, + StyledProperty property, IObservable> source) : base(frame, property, source) { } - public new StyledPropertyBase Property => (StyledPropertyBase)base.Property; + public new StyledProperty Property => (StyledProperty)base.Property; protected override BindingValue ConvertAndValidate(T value) { diff --git a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs index 5c5591dcb5..372a808fb2 100644 --- a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs +++ b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs @@ -26,7 +26,7 @@ namespace Avalonia.PropertyStore [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] public static bool TryConvertAndValidate( - StyledPropertyBase property, + StyledProperty property, object? value, [MaybeNullWhen(false)] out T result) { diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 92e5288255..f36a96992b 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -43,7 +43,7 @@ namespace Avalonia.PropertyStore } public IDisposable AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable> source, BindingPriority priority) { @@ -71,7 +71,7 @@ namespace Avalonia.PropertyStore } public IDisposable AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable source, BindingPriority priority) { @@ -99,7 +99,7 @@ namespace Avalonia.PropertyStore } public IDisposable AddBinding( - StyledPropertyBase property, + StyledProperty property, IObservable source, BindingPriority priority) { @@ -165,7 +165,7 @@ namespace Avalonia.PropertyStore } } - public IDisposable? SetValue(StyledPropertyBase property, T value, BindingPriority priority) + public IDisposable? SetValue(StyledProperty property, T value, BindingPriority priority) { if (property.ValidateValue?.Invoke(value) == false) { @@ -219,7 +219,7 @@ namespace Avalonia.PropertyStore return GetDefaultValue(property); } - public T GetValue(StyledPropertyBase property) + public T GetValue(StyledProperty property) { if (_effectiveValues.TryGetValue(property, out var v)) return ((EffectiveValue)v).Value; @@ -248,7 +248,7 @@ namespace Avalonia.PropertyStore v.CoerceValue(this, property); } - public Optional GetBaseValue(StyledPropertyBase property) + public Optional GetBaseValue(StyledProperty property) { if (TryGetEffectiveValue(property, out var v) && ((EffectiveValue)v).TryGetBaseValue(out var baseValue)) @@ -450,7 +450,7 @@ namespace Avalonia.PropertyStore /// The old value of the property. /// The effective value instance. public void OnInheritedEffectiveValueChanged( - StyledPropertyBase property, + StyledProperty property, T oldValue, EffectiveValue value) { @@ -475,7 +475,7 @@ namespace Avalonia.PropertyStore /// /// The property whose value changed. /// The old value of the property. - public void OnInheritedEffectiveValueDisposed(StyledPropertyBase property, T oldValue) + public void OnInheritedEffectiveValueDisposed(StyledProperty property, T oldValue) { Debug.Assert(property.Inherits); @@ -520,7 +520,7 @@ namespace Avalonia.PropertyStore /// The old value of the property. /// The new value of the property. public void OnAncestorInheritedValueChanged( - StyledPropertyBase property, + StyledProperty property, T oldValue, T newValue) { diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs index 019ed09c20..79d1b9202d 100644 --- a/src/Avalonia.Base/StyledProperty.cs +++ b/src/Avalonia.Base/StyledProperty.cs @@ -1,14 +1,18 @@ using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Data; +using Avalonia.PropertyStore; +using Avalonia.Utilities; namespace Avalonia { /// /// A styled avalonia property. /// - public class StyledProperty : StyledPropertyBase + public class StyledProperty : AvaloniaProperty, IStyledPropertyAccessor { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name of the property. /// The type of the class that registers the property. @@ -23,20 +27,30 @@ namespace Avalonia bool inherits = false, Func? validate = null, Action? notifying = null) - : base(name, ownerType, metadata, inherits, validate, notifying) + : base(name, ownerType, metadata, notifying) { + Inherits = inherits; + ValidateValue = validate; + HasCoercion |= metadata.CoerceValue != null; + + if (validate?.Invoke(metadata.DefaultValue) == false) + { + throw new ArgumentException( + $"'{metadata.DefaultValue}' is not a valid default value for '{name}'."); + } } /// - /// Initializes a new instance of the class. + /// Gets the value validation callback for the property. /// - /// The property to add the owner to. - /// The type of the class that registers the property. - internal StyledProperty(StyledPropertyBase source, Type ownerType) - : base(source, ownerType) - { - } - + public Func? ValidateValue { get; } + + /// + /// Gets a value indicating whether this property has any value coercion callbacks defined + /// in its metadata. + /// + internal bool HasCoercion { get; private set; } + /// /// Registers the property on another type. /// @@ -47,5 +61,177 @@ namespace Avalonia AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); return this; } + + public TValue CoerceValue(AvaloniaObject instance, TValue baseValue) + { + var metadata = GetMetadata(instance.GetType()); + + if (metadata.CoerceValue != null) + { + return metadata.CoerceValue.Invoke(instance, baseValue); + } + + return baseValue; + } + + /// + /// Gets the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public TValue GetDefaultValue(Type type) + { + return GetMetadata(type).DefaultValue; + } + + /// + /// Gets the property metadata for the specified type. + /// + /// The type. + /// + /// The property metadata. + /// + public new StyledPropertyMetadata GetMetadata(Type type) + { + _ = type ?? throw new ArgumentNullException(nameof(type)); + return (StyledPropertyMetadata)base.GetMetadata(type); + } + + /// + /// Overrides the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public void OverrideDefaultValue(TValue defaultValue) where T : AvaloniaObject + { + OverrideDefaultValue(typeof(T), defaultValue); + } + + /// + /// Overrides the default value for the property on the specified type. + /// + /// The type. + /// The default value. + public void OverrideDefaultValue(Type type, TValue defaultValue) + { + OverrideMetadata(type, new StyledPropertyMetadata(defaultValue)); + } + + /// + /// Overrides the metadata for the property on the specified type. + /// + /// The type. + /// The metadata. + public void OverrideMetadata(StyledPropertyMetadata metadata) where T : AvaloniaObject + { + base.OverrideMetadata(typeof(T), metadata); + } + + /// + /// Overrides the metadata for the property on the specified type. + /// + /// The type. + /// The metadata. + public void OverrideMetadata(Type type, StyledPropertyMetadata metadata) + { + if (ValidateValue != null) + { + if (!ValidateValue(metadata.DefaultValue)) + { + throw new ArgumentException( + $"'{metadata.DefaultValue}' is not a valid default value for '{Name}'."); + } + } + + HasCoercion |= metadata.CoerceValue != null; + + base.OverrideMetadata(type, metadata); + } + + /// + /// Gets the string representation of the property. + /// + /// The property's string representation. + public override string ToString() + { + return Name; + } + + /// + object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); + + bool IStyledPropertyAccessor.ValidateValue(object? value) + { + if (value is null && !typeof(TValue).IsValueType) + return ValidateValue?.Invoke(default!) ?? true; + if (value is TValue typed) + return ValidateValue?.Invoke(typed) ?? true; + return false; + } + + internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) + { + return new EffectiveValue(o, this); + } + + /// + internal override void RouteClearValue(AvaloniaObject o) + { + o.ClearValue(this); + } + + /// + internal override object? RouteGetValue(AvaloniaObject o) + { + return o.GetValue(this); + } + + /// + internal override object? RouteGetBaseValue(AvaloniaObject o) + { + var value = o.GetBaseValue(this); + return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue; + } + + /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] + internal override IDisposable? RouteSetValue( + AvaloniaObject target, + object? value, + BindingPriority priority) + { + if (value == BindingOperations.DoNothing) + { + return null; + } + else if (value == UnsetValue) + { + target.ClearValue(this); + return null; + } + else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted)) + { + return target.SetValue(this, (TValue)converted!, priority); + } + else + { + var type = value?.GetType().FullName ?? "(null)"; + throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})"); + } + } + + internal override IDisposable RouteBind( + AvaloniaObject target, + IObservable source, + BindingPriority priority) + { + return target.Bind(this, source, priority); + } + + private object? GetDefaultBoxedValue(Type type) + { + _ = type ?? throw new ArgumentNullException(nameof(type)); + return GetMetadata(type).DefaultValue; + } } } diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs deleted file mode 100644 index b39f45189c..0000000000 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Data; -using Avalonia.PropertyStore; -using Avalonia.Utilities; - -namespace Avalonia -{ - /// - /// Base class for styled properties. - /// - public abstract class StyledPropertyBase : AvaloniaProperty, IStyledPropertyAccessor - { - /// - /// Initializes a new instance of the class. - /// - /// The name of the property. - /// The type of the class that registers the property. - /// The property metadata. - /// Whether the property inherits its value. - /// A value validation callback. - /// A callback. - protected StyledPropertyBase( - string name, - Type ownerType, - StyledPropertyMetadata metadata, - bool inherits = false, - Func? validate = null, - Action? notifying = null) - : base(name, ownerType, metadata, notifying) - { - Inherits = inherits; - ValidateValue = validate; - HasCoercion |= metadata.CoerceValue != null; - - if (validate?.Invoke(metadata.DefaultValue) == false) - { - throw new ArgumentException( - $"'{metadata.DefaultValue}' is not a valid default value for '{name}'."); - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The property to add the owner to. - /// The type of the class that registers the property. - protected StyledPropertyBase(StyledPropertyBase source, Type ownerType) - : base(source, ownerType, null) - { - Inherits = source.Inherits; - } - - /// - /// Gets the value validation callback for the property. - /// - public Func? ValidateValue { get; } - - /// - /// Gets a value indicating whether this property has any value coercion callbacks defined - /// in its metadata. - /// - internal bool HasCoercion { get; private set; } - - public TValue CoerceValue(AvaloniaObject instance, TValue baseValue) - { - var metadata = GetMetadata(instance.GetType()); - - if (metadata.CoerceValue != null) - { - return metadata.CoerceValue.Invoke(instance, baseValue); - } - - return baseValue; - } - - /// - /// Gets the default value for the property on the specified type. - /// - /// The type. - /// The default value. - public TValue GetDefaultValue(Type type) - { - return GetMetadata(type).DefaultValue; - } - - /// - /// Gets the property metadata for the specified type. - /// - /// The type. - /// - /// The property metadata. - /// - public new StyledPropertyMetadata GetMetadata(Type type) - { - _ = type ?? throw new ArgumentNullException(nameof(type)); - return (StyledPropertyMetadata)base.GetMetadata(type); - } - - /// - /// Overrides the default value for the property on the specified type. - /// - /// The type. - /// The default value. - public void OverrideDefaultValue(TValue defaultValue) where T : AvaloniaObject - { - OverrideDefaultValue(typeof(T), defaultValue); - } - - /// - /// Overrides the default value for the property on the specified type. - /// - /// The type. - /// The default value. - public void OverrideDefaultValue(Type type, TValue defaultValue) - { - OverrideMetadata(type, new StyledPropertyMetadata(defaultValue)); - } - - /// - /// Overrides the metadata for the property on the specified type. - /// - /// The type. - /// The metadata. - public void OverrideMetadata(StyledPropertyMetadata metadata) where T : AvaloniaObject - { - base.OverrideMetadata(typeof(T), metadata); - } - - /// - /// Overrides the metadata for the property on the specified type. - /// - /// The type. - /// The metadata. - public void OverrideMetadata(Type type, StyledPropertyMetadata metadata) - { - if (ValidateValue != null) - { - if (!ValidateValue(metadata.DefaultValue)) - { - throw new ArgumentException( - $"'{metadata.DefaultValue}' is not a valid default value for '{Name}'."); - } - } - - HasCoercion |= metadata.CoerceValue != null; - - base.OverrideMetadata(type, metadata); - } - - /// - /// Gets the string representation of the property. - /// - /// The property's string representation. - public override string ToString() - { - return Name; - } - - /// - object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); - - bool IStyledPropertyAccessor.ValidateValue(object? value) - { - if (value is null && !typeof(TValue).IsValueType) - return ValidateValue?.Invoke(default!) ?? true; - if (value is TValue typed) - return ValidateValue?.Invoke(typed) ?? true; - return false; - } - - internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) - { - return new EffectiveValue(o, this); - } - - /// - internal override void RouteClearValue(AvaloniaObject o) - { - o.ClearValue(this); - } - - /// - internal override object? RouteGetValue(AvaloniaObject o) - { - return o.GetValue(this); - } - - /// - internal override object? RouteGetBaseValue(AvaloniaObject o) - { - var value = o.GetBaseValue(this); - return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue; - } - - /// - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] - internal override IDisposable? RouteSetValue( - AvaloniaObject target, - object? value, - BindingPriority priority) - { - if (value == BindingOperations.DoNothing) - { - return null; - } - else if (value == UnsetValue) - { - target.ClearValue(this); - return null; - } - else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted)) - { - return target.SetValue(this, (TValue)converted!, priority); - } - else - { - var type = value?.GetType().FullName ?? "(null)"; - throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})"); - } - } - - internal override IDisposable RouteBind( - AvaloniaObject target, - IObservable source, - BindingPriority priority) - { - return target.Bind(this, source, priority); - } - - private object? GetDefaultBoxedValue(Type type) - { - _ = type ?? throw new ArgumentNullException(nameof(type)); - return GetMetadata(type).DefaultValue; - } - } -} diff --git a/src/Avalonia.Base/Styling/PropertySetterInstance.cs b/src/Avalonia.Base/Styling/PropertySetterInstance.cs index 68a9b8aafe..af5540ecf0 100644 --- a/src/Avalonia.Base/Styling/PropertySetterInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterInstance.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling ISetterInstance { private readonly StyledElement _target; - private readonly StyledPropertyBase? _styledProperty; + private readonly StyledProperty? _styledProperty; private readonly DirectPropertyBase? _directProperty; private readonly T _value; private IDisposable? _subscription; @@ -22,7 +22,7 @@ namespace Avalonia.Styling public PropertySetterInstance( StyledElement target, - StyledPropertyBase property, + StyledProperty property, T value) { _target = target; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index aab6239a35..0b61316603 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -126,7 +126,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers AvaloniaObjectSetStyledPropertyValue = AvaloniaObject .FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "SetValue" && m.Parameters.Count == 3 - && m.Parameters[0].Name == "StyledPropertyBase`1" + && m.Parameters[0].Name == "StyledProperty`1" && m.Parameters[2].Equals(BindingPriority)); IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding"); IDisposable = cfg.TypeSystem.GetType("System.IDisposable"); From 65d8e46fa67766db48453b939ef4807365073270 Mon Sep 17 00:00:00 2001 From: Dmitry Zhelnin Date: Mon, 23 Jan 2023 14:42:29 +0300 Subject: [PATCH 4/4] VirtualizingStackPanel: fix selection wrapping --- src/Avalonia.Controls/VirtualizingStackPanel.cs | 2 +- tests/Avalonia.Controls.UnitTests/ListBoxTests.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 3f539ce198..f2b42b0b7e 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -226,7 +226,7 @@ namespace Avalonia.Controls { if (toIndex < 0) toIndex = count - 1; - else if (toIndex >= count - 1) + else if (toIndex >= count) toIndex = 0; } diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 3f1a3b6342..8170545f68 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -759,6 +759,7 @@ namespace Avalonia.Controls.UnitTests var lbItems = target.GetLogicalChildren().OfType().ToArray(); var first = lbItems.First(); + var beforeLast = lbItems[^2]; var last = lbItems.Last(); first.Focus(); @@ -769,6 +770,12 @@ namespace Avalonia.Controls.UnitTests RaiseKeyEvent(target, Key.Up); Assert.Equal(true, last.IsSelected); + RaiseKeyEvent(target, Key.Up); + Assert.Equal(true, beforeLast.IsSelected); + + RaiseKeyEvent(target, Key.Down); + Assert.Equal(true, last.IsSelected); + RaiseKeyEvent(target, Key.Down); Assert.Equal(true, first.IsSelected);