Przeglądaj źródła

Implemented coercion.

Steven Kirk 3 lat temu
rodzic
commit
fb3e9d71b7

+ 1 - 5
src/Avalonia.Base/AvaloniaObject.cs

@@ -462,11 +462,7 @@ namespace Avalonia
         /// Coerces the specified <see cref="AvaloniaProperty"/>.
         /// </summary>
         /// <param name="property">The property.</param>
-        public void CoerceValue(AvaloniaProperty property)
-        {
-            throw new NotImplementedException();
-            ////_values?.CoerceValue(property);
-        }
+        public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
 
         /// <inheritdoc/>
         internal void AddInheritanceChild(AvaloniaObject child)

+ 7 - 0
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@@ -103,6 +103,13 @@ namespace Avalonia.PropertyStore
             EffectiveValue? oldValue,
             EffectiveValue? newValue);
 
+        /// <summary>
+        /// Coerces the property value.
+        /// </summary>
+        /// <param name="owner">The associated value store.</param>
+        /// <param name="property">The property to coerce.</param>
+        public abstract void CoerceValue(ValueStore owner, AvaloniaProperty property);
+
         /// <summary>
         /// Disposes the effective value, raising <see cref="AvaloniaObject.PropertyChanged"/>
         /// where necessary.

+ 73 - 11
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using Avalonia.Data;
@@ -15,12 +16,31 @@ namespace Avalonia.PropertyStore
     internal sealed class EffectiveValue<T> : EffectiveValue
     {
         private T? _baseValue;
+        private UncommonFields? _uncommon;
 
-        public EffectiveValue(T value, BindingPriority priority)
+        public EffectiveValue(
+            AvaloniaObject owner,
+            StyledPropertyBase<T> property,
+            T value, 
+            BindingPriority priority)
         {
             Value = value;
             Priority = priority;
 
+            if (property.HasCoercion &&
+                property.GetMetadata(owner.GetType()) is { } metadata &&
+                metadata.CoerceValue is { } coerce)
+            {
+                _uncommon = new()
+                {
+                    _coerce = coerce,
+                    _uncoercedValue = value,
+                    _uncoercedBaseValue = value,
+                };
+
+                value = coerce(owner, value);
+            }
+
             if (priority >= BindingPriority.LocalValue && priority < BindingPriority.Inherited)
             {
                 _baseValue = value;
@@ -88,19 +108,27 @@ namespace Avalonia.PropertyStore
             var oldValue = Value;
             var valueChanged = false;
             var baseValueChanged = false;
+            var v = value;
+
+            if (_uncommon?._coerce is { } coerce)
+                v = coerce(owner.Owner, value);
 
             if (priority <= Priority)
             {
-                valueChanged = !EqualityComparer<T>.Default.Equals(Value, value);
-                Value = value;
+                valueChanged = !EqualityComparer<T>.Default.Equals(Value, v);
+                Value = v;
                 Priority = priority;
+                if (_uncommon is not null)
+                    _uncommon._uncoercedValue = value;
             }
 
             if (priority <= BasePriority && priority >= BindingPriority.LocalValue)
             {
-                baseValueChanged = !EqualityComparer<T>.Default.Equals(_baseValue, value);
-                _baseValue = value;
+                baseValueChanged = !EqualityComparer<T>.Default.Equals(_baseValue, v);
+                _baseValue = v;
                 BasePriority = priority;
+                if (_uncommon is not null)
+                    _uncommon._uncoercedBaseValue = value;
             }
 
             if (valueChanged)
@@ -135,22 +163,36 @@ namespace Avalonia.PropertyStore
         {
             Debug.Assert(priority < BindingPriority.Inherited);
             Debug.Assert(basePriority > BindingPriority.Animation);
+            Debug.Assert(priority <= basePriority);
 
             var oldValue = Value;
             var valueChanged = false;
             var baseValueChanged = false;
+            var v = value;
+            var bv = baseValue;
 
-            if (!EqualityComparer<T>.Default.Equals(Value, value))
+            if (_uncommon?._coerce is { } coerce)
             {
-                Value = value;
+                v = coerce(owner.Owner, value);
+                bv = coerce(owner.Owner, baseValue);
+            }
+
+            if (priority != BindingPriority.Unset && !EqualityComparer<T>.Default.Equals(Value, v))
+            {
+                Value = v;
                 valueChanged = true;
+                if (_uncommon is not null)
+                    _uncommon._uncoercedValue = value;
             }
 
-            if (BasePriority == BindingPriority.Unset || 
-                !EqualityComparer<T>.Default.Equals(_baseValue, baseValue))
+            if (priority != BindingPriority.Unset &&
+                (BasePriority == BindingPriority.Unset || 
+                 !EqualityComparer<T>.Default.Equals(_baseValue, bv)))
             {
-                _baseValue = value;
+                _baseValue = v;
                 baseValueChanged = true;
+                if (_uncommon is not null)
+                    _uncommon._uncoercedValue = baseValue;
             }
 
             Priority = priority;
@@ -193,6 +235,19 @@ namespace Avalonia.PropertyStore
             }
         }
 
+        public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
+        {
+            if (_uncommon is null)
+                return;
+            SetAndRaise(
+                owner, 
+                (StyledPropertyBase<T>)property, 
+                _uncommon._uncoercedValue!, 
+                Priority, 
+                _uncommon._uncoercedBaseValue!,
+                BasePriority);
+        }
+
         public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property)
         {
             DisposeAndRaiseUnset(owner, (StyledPropertyBase<T>)property);
@@ -216,5 +271,12 @@ namespace Avalonia.PropertyStore
         {
             return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue;
         }
+
+        private class UncommonFields
+        {
+            public Func<IAvaloniaObject, T, T>? _coerce;
+            public T? _uncoercedValue;
+            public T? _uncoercedBaseValue;
+        }
     }
 }

+ 7 - 1
src/Avalonia.Base/PropertyStore/ValueStore.cs

@@ -189,6 +189,12 @@ namespace Avalonia.PropertyStore
             return false;
         }
 
+        public void CoerceValue(AvaloniaProperty property)
+        {
+            if (_effectiveValues is not null && _effectiveValues.TryGetValue(property, out var v))
+                v.CoerceValue(this, property);
+        }
+
         public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property)
         {
             if (TryGetEffectiveValue(property, out var v) &&
@@ -628,7 +634,7 @@ namespace Avalonia.PropertyStore
         {
             Debug.Assert(priority < BindingPriority.Inherited);
             var defaultValue = property.GetDefaultValue(Owner.GetType());
-            var effectiveValue = new EffectiveValue<T>(defaultValue, BindingPriority.Unset);
+            var effectiveValue = new EffectiveValue<T>(Owner, property, defaultValue, BindingPriority.Unset);
             AddEffectiveValue(property, effectiveValue);
             effectiveValue.SetAndRaise(this, property, value, priority);
         }

+ 1 - 1
src/Avalonia.Base/StyledPropertyBase.cs

@@ -183,7 +183,7 @@ namespace Avalonia
 
         internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
         {
-            return new EffectiveValue<TValue>(GetDefaultValue(o.GetType()), BindingPriority.Unset);
+            return new EffectiveValue<TValue>(o, this, GetDefaultValue(o.GetType()), BindingPriority.Unset);
         }
 
         /// <inheritdoc/>

+ 1 - 1
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs

@@ -73,7 +73,7 @@ namespace Avalonia.Base.UnitTests
             var source1 = new Subject<BindingValue<int>>();
             var source2 = new Subject<BindingValue<int>>();
 
-            target.Bind(Class1.FooProperty, source1);
+            target.Bind(Class1.FooProperty, source1, BindingPriority.Style);
             source1.OnNext(150);
 
             target.Bind(Class1.FooProperty, source2);