diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 60e7b2aeef..42761ab92e 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -462,11 +462,7 @@ namespace Avalonia /// Coerces the specified . /// /// The property. - public void CoerceValue(AvaloniaProperty property) - { - throw new NotImplementedException(); - ////_values?.CoerceValue(property); - } + public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property); /// internal void AddInheritanceChild(AvaloniaObject child) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs index 27d3f319d5..9ed4c67fd6 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs @@ -103,6 +103,13 @@ namespace Avalonia.PropertyStore EffectiveValue? oldValue, EffectiveValue? newValue); + /// + /// Coerces the property value. + /// + /// The associated value store. + /// The property to coerce. + public abstract void CoerceValue(ValueStore owner, AvaloniaProperty property); + /// /// Disposes the effective value, raising /// where necessary. diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 2b3865d23d..d9368b57da 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/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 : EffectiveValue { private T? _baseValue; + private UncommonFields? _uncommon; - public EffectiveValue(T value, BindingPriority priority) + public EffectiveValue( + AvaloniaObject owner, + StyledPropertyBase 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.Default.Equals(Value, value); - Value = value; + valueChanged = !EqualityComparer.Default.Equals(Value, v); + Value = v; Priority = priority; + if (_uncommon is not null) + _uncommon._uncoercedValue = value; } if (priority <= BasePriority && priority >= BindingPriority.LocalValue) { - baseValueChanged = !EqualityComparer.Default.Equals(_baseValue, value); - _baseValue = value; + baseValueChanged = !EqualityComparer.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.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.Default.Equals(Value, v)) + { + Value = v; valueChanged = true; + if (_uncommon is not null) + _uncommon._uncoercedValue = value; } - if (BasePriority == BindingPriority.Unset || - !EqualityComparer.Default.Equals(_baseValue, baseValue)) + if (priority != BindingPriority.Unset && + (BasePriority == BindingPriority.Unset || + !EqualityComparer.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)property, + _uncommon._uncoercedValue!, + Priority, + _uncommon._uncoercedBaseValue!, + BasePriority); + } + public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property) { DisposeAndRaiseUnset(owner, (StyledPropertyBase)property); @@ -216,5 +271,12 @@ namespace Avalonia.PropertyStore { return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue; } + + private class UncommonFields + { + public Func? _coerce; + public T? _uncoercedValue; + public T? _uncoercedBaseValue; + } } } diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index aa9d7525a3..b5db37ca20 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/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 GetBaseValue(StyledPropertyBase 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(defaultValue, BindingPriority.Unset); + var effectiveValue = new EffectiveValue(Owner, property, defaultValue, BindingPriority.Unset); AddEffectiveValue(property, effectiveValue); effectiveValue.SetAndRaise(this, property, value, priority); } diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index eed9df71e0..2dcd468f15 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -183,7 +183,7 @@ namespace Avalonia internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) { - return new EffectiveValue(GetDefaultValue(o.GetType()), BindingPriority.Unset); + return new EffectiveValue(o, this, GetDefaultValue(o.GetType()), BindingPriority.Unset); } /// diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs index e2e87d4dfa..7a35fc89b7 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs @@ -73,7 +73,7 @@ namespace Avalonia.Base.UnitTests var source1 = new Subject>(); var source2 = new Subject>(); - target.Bind(Class1.FooProperty, source1); + target.Bind(Class1.FooProperty, source1, BindingPriority.Style); source1.OnNext(150); target.Bind(Class1.FooProperty, source2);