diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 58ccb53744..bc1e95805f 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -16,7 +16,7 @@ namespace Avalonia /// /// This class is analogous to DependencyObject in WPF. /// - public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IValueSink + public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged { private AvaloniaObject? _inheritanceParent; private List? _directBindings; @@ -524,7 +524,7 @@ namespace Avalonia return _propertyChanged?.GetInvocationList(); } - void IValueSink.ValueChanged(AvaloniaPropertyChangedEventArgs change) + internal void ValueChanged(AvaloniaPropertyChangedEventArgs change) { var property = (StyledPropertyBase)change.Property; @@ -563,7 +563,7 @@ namespace Avalonia } } - void IValueSink.Completed( + internal void Completed( StyledPropertyBase property, IPriorityValueEntry entry, Optional oldValue) @@ -574,7 +574,7 @@ namespace Avalonia oldValue, default, BindingPriority.Unset); - ((IValueSink)this).ValueChanged(change); + ValueChanged(change); } /// diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs index cffbaed6b0..9a25e98a23 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs @@ -18,19 +18,19 @@ namespace Avalonia.PropertyStore /// The property type. internal class BindingEntry : IBindingEntry, IPriorityValueEntry, IObserver> { - private readonly IAvaloniaObject _owner; - private IValueSink _sink; + private readonly AvaloniaObject _owner; + private ValueOwner _sink; private IDisposable? _subscription; private bool _isSubscribed; private bool _batchUpdate; private Optional _value; public BindingEntry( - IAvaloniaObject owner, + AvaloniaObject owner, StyledPropertyBase property, IObservable> source, BindingPriority priority, - IValueSink sink) + ValueOwner sink) { _owner = owner; Property = property; @@ -50,7 +50,7 @@ namespace Avalonia.PropertyStore { _batchUpdate = false; - if (_sink is ValueStore) + if (_sink.IsValueStore) Start(); } @@ -113,16 +113,15 @@ namespace Avalonia.PropertyStore } } - public void Reparent(IValueSink sink) => _sink = sink; + public void Reparent(PriorityValue parent) => _sink = new(parent); public void RaiseValueChanged( - IValueSink sink, - IAvaloniaObject owner, + AvaloniaObject owner, AvaloniaProperty property, Optional oldValue, Optional newValue) { - sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( owner, (AvaloniaProperty)property, oldValue.Cast(), diff --git a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs index c7fbf56abc..4116f4abd9 100644 --- a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs @@ -18,14 +18,14 @@ namespace Avalonia.PropertyStore /// The property type. internal class ConstantValueEntry : IPriorityValueEntry, IConstantValueEntry { - private IValueSink _sink; + private ValueOwner _sink; private Optional _value; public ConstantValueEntry( StyledPropertyBase property, T value, BindingPriority priority, - IValueSink sink) + ValueOwner sink) { Property = property; _value = value; @@ -37,7 +37,7 @@ namespace Avalonia.PropertyStore StyledPropertyBase property, Optional value, BindingPriority priority, - IValueSink sink) + ValueOwner sink) { Property = property; _value = value; @@ -62,17 +62,16 @@ namespace Avalonia.PropertyStore _sink.Completed(Property, this, oldValue); } - public void Reparent(IValueSink sink) => _sink = sink; + public void Reparent(PriorityValue sink) => _sink = new(sink); public void Start() { } public void RaiseValueChanged( - IValueSink sink, - IAvaloniaObject owner, + AvaloniaObject owner, AvaloniaProperty property, Optional oldValue, Optional newValue) { - sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( owner, (AvaloniaProperty)property, oldValue.Cast(), diff --git a/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs b/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs index 26665ab683..45bbd0cda5 100644 --- a/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs @@ -5,7 +5,6 @@ /// internal interface IPriorityValueEntry : IValue { - void Reparent(IValueSink sink); } /// @@ -14,5 +13,6 @@ /// The property type. internal interface IPriorityValueEntry : IPriorityValueEntry, IValue { + void Reparent(PriorityValue parent); } } diff --git a/src/Avalonia.Base/PropertyStore/IValue.cs b/src/Avalonia.Base/PropertyStore/IValue.cs index a4ec06e64e..b493df92e6 100644 --- a/src/Avalonia.Base/PropertyStore/IValue.cs +++ b/src/Avalonia.Base/PropertyStore/IValue.cs @@ -11,8 +11,7 @@ namespace Avalonia.PropertyStore Optional GetValue(); void Start(); void RaiseValueChanged( - IValueSink sink, - IAvaloniaObject owner, + AvaloniaObject owner, AvaloniaProperty property, Optional oldValue, Optional newValue); diff --git a/src/Avalonia.Base/PropertyStore/IValueSink.cs b/src/Avalonia.Base/PropertyStore/IValueSink.cs deleted file mode 100644 index 738fdef267..0000000000 --- a/src/Avalonia.Base/PropertyStore/IValueSink.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Avalonia.Data; - -namespace Avalonia.PropertyStore -{ - /// - /// Represents an entity that can receive change notifications in a . - /// - internal interface IValueSink - { - void ValueChanged(AvaloniaPropertyChangedEventArgs change); - - void Completed( - StyledPropertyBase property, - IPriorityValueEntry entry, - Optional oldValue); - } -} diff --git a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs index cb5f3556d1..13ca69681f 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs @@ -25,13 +25,12 @@ namespace Avalonia.PropertyStore public void Start() { } public void RaiseValueChanged( - IValueSink sink, - IAvaloniaObject owner, + AvaloniaObject owner, AvaloniaProperty property, Optional oldValue, Optional newValue) { - sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( owner, (AvaloniaProperty)property, oldValue.Cast(), diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs index 03c83d00ed..112cf6619f 100644 --- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs +++ b/src/Avalonia.Base/PropertyStore/PriorityValue.cs @@ -23,10 +23,10 @@ namespace Avalonia.PropertyStore /// entries (sorted first by priority and then in the order /// they were added) plus a local value. /// - internal class PriorityValue : IPriorityValue, IValue, IValueSink, IBatchUpdate + internal class PriorityValue : IPriorityValue, IValue, IBatchUpdate { - private readonly IAvaloniaObject _owner; - private readonly IValueSink _sink; + private readonly AvaloniaObject _owner; + private readonly ValueStore _store; private readonly List> _entries = new List>(); private readonly Func? _coerceValue; private Optional _localValue; @@ -35,13 +35,13 @@ namespace Avalonia.PropertyStore private bool _batchUpdate; public PriorityValue( - IAvaloniaObject owner, + AvaloniaObject owner, StyledPropertyBase property, - IValueSink sink) + ValueStore store) { _owner = owner; Property = property; - _sink = sink; + _store = store; if (property.HasCoercion) { @@ -51,11 +51,11 @@ namespace Avalonia.PropertyStore } public PriorityValue( - IAvaloniaObject owner, + AvaloniaObject owner, StyledPropertyBase property, - IValueSink sink, + ValueStore store, IPriorityValueEntry existing) - : this(owner, property, sink) + : this(owner, property, store) { existing.Reparent(this); _entries.Add(existing); @@ -82,9 +82,9 @@ namespace Avalonia.PropertyStore } public PriorityValue( - IAvaloniaObject owner, + AvaloniaObject owner, StyledPropertyBase property, - IValueSink sink, + ValueStore sink, LocalValueEntry existing) : this(owner, property, sink) { @@ -155,7 +155,7 @@ namespace Avalonia.PropertyStore else { var insert = FindInsertPoint(priority); - var entry = new ConstantValueEntry(Property, value, priority, this); + var entry = new ConstantValueEntry(Property, value, priority, new ValueOwner(this)); _entries.Insert(insert, entry); result = entry; } @@ -172,7 +172,7 @@ namespace Avalonia.PropertyStore public BindingEntry AddBinding(IObservable> source, BindingPriority priority) { - var binding = new BindingEntry(_owner, Property, source, priority, this); + var binding = new BindingEntry(_owner, Property, source, priority, new(this)); var insert = FindInsertPoint(binding.Priority); _entries.Insert(insert, binding); @@ -193,13 +193,12 @@ namespace Avalonia.PropertyStore public void Start() => UpdateEffectiveValue(null); public void RaiseValueChanged( - IValueSink sink, - IAvaloniaObject owner, + AvaloniaObject owner, AvaloniaProperty property, Optional oldValue, Optional newValue) { - sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( owner, (AvaloniaProperty)property, oldValue.Cast(), @@ -207,7 +206,7 @@ namespace Avalonia.PropertyStore Priority)); } - void IValueSink.ValueChanged(AvaloniaPropertyChangedEventArgs change) + public void ValueChanged(AvaloniaPropertyChangedEventArgs change) { if (change.Priority == BindingPriority.LocalValue) { @@ -220,22 +219,15 @@ namespace Avalonia.PropertyStore } } - void IValueSink.Completed( - StyledPropertyBase property, - IPriorityValueEntry entry, - Optional oldValue) + public void Completed(IPriorityValueEntry entry, Optional oldValue) { _entries.Remove((IPriorityValueEntry)entry); - - if (oldValue is Optional o) - { - UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( - _owner, - Property, - o, - default, - entry.Priority)); - } + UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs( + _owner, + Property, + oldValue, + default, + entry.Priority)); } private int FindInsertPoint(BindingPriority priority) @@ -315,7 +307,7 @@ namespace Avalonia.PropertyStore var old = _value; _value = value; - _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + _store.ValueChanged(new AvaloniaPropertyChangedEventArgs( _owner, Property, old, @@ -326,7 +318,7 @@ namespace Avalonia.PropertyStore { change.MarkNonEffectiveValue(); change.SetOldValue(default); - _sink.ValueChanged(change); + _store.ValueChanged(change); } } } diff --git a/src/Avalonia.Base/PropertyStore/ValueOwner.cs b/src/Avalonia.Base/PropertyStore/ValueOwner.cs new file mode 100644 index 0000000000..c68435f7a5 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/ValueOwner.cs @@ -0,0 +1,45 @@ +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + /// + /// Represents a union type of and , + /// which are the valid owners of a value store . + /// + /// The value type. + internal readonly struct ValueOwner + { + private readonly ValueStore? _store; + private readonly PriorityValue? _priorityValue; + + public ValueOwner(ValueStore o) + { + _store = o; + _priorityValue = null; + } + + public ValueOwner(PriorityValue v) + { + _store = null; + _priorityValue = v; + } + + public bool IsValueStore => _store is not null; + + public void Completed(StyledPropertyBase property, IPriorityValueEntry entry, Optional oldValue) + { + if (_store is not null) + _store?.Completed(property, entry, oldValue); + else + _priorityValue!.Completed(entry, oldValue); + } + + public void ValueChanged(AvaloniaPropertyChangedEventArgs e) + { + if (_store is not null) + _store?.ValueChanged(e); + else + _priorityValue!.ValueChanged(e); + } + } +} diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 67149b0988..69c644dff9 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -21,16 +21,15 @@ namespace Avalonia /// - For a single binding it will be an instance of /// - For all other cases it will be an instance of /// - internal class ValueStore : IValueSink + internal class ValueStore { private readonly AvaloniaObject _owner; - private readonly IValueSink _sink; private readonly AvaloniaPropertyValueStore _values; private BatchUpdate? _batchUpdate; public ValueStore(AvaloniaObject owner) { - _sink = _owner = owner; + _owner = owner; _values = new AvaloniaPropertyValueStore(); } @@ -122,7 +121,7 @@ namespace Avalonia } else { - var entry = new ConstantValueEntry(property, value, priority, this); + var entry = new ConstantValueEntry(property, value, priority, new(this)); AddValue(property, entry); NotifyValueChanged(property, default, value, priority); result = entry; @@ -151,7 +150,7 @@ namespace Avalonia } else { - var entry = new BindingEntry(_owner, property, source, priority, this); + var entry = new BindingEntry(_owner, property, source, priority, new(this)); AddValue(property, entry); return entry; } @@ -187,7 +186,7 @@ namespace Avalonia // so there's no way to mark them for removal at the end of a batch update. Instead convert // them to a constant value entry with Unset priority in the event of a local value being // cleared during a batch update. - var sentinel = new ConstantValueEntry(property, Optional.Empty, BindingPriority.Unset, _sink); + var sentinel = new ConstantValueEntry(property, Optional.Empty, BindingPriority.Unset, new(this)); _values.SetValue(property, sentinel); } @@ -222,7 +221,7 @@ namespace Avalonia return null; } - void IValueSink.ValueChanged(AvaloniaPropertyChangedEventArgs change) + public void ValueChanged(AvaloniaPropertyChangedEventArgs change) { if (_batchUpdate is object) { @@ -233,11 +232,11 @@ namespace Avalonia } else { - _sink.ValueChanged(change); + _owner.ValueChanged(change); } } - void IValueSink.Completed( + public void Completed( StyledPropertyBase property, IPriorityValueEntry entry, Optional oldValue) @@ -248,7 +247,7 @@ namespace Avalonia if (_batchUpdate is null) { _values.Remove(property); - _sink.Completed(property, entry, oldValue); + _owner.Completed(property, entry, oldValue); } else { @@ -352,7 +351,7 @@ namespace Avalonia { if (_batchUpdate is null) { - _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs( + _owner.ValueChanged(new AvaloniaPropertyChangedEventArgs( _owner, property, oldValue, @@ -451,7 +450,7 @@ namespace Avalonia }; // Call _sink.ValueChanged with an appropriately typed AvaloniaPropertyChangedEventArgs. - slot.RaiseValueChanged(_owner._sink, _owner._owner, entry.property, oldValue, newValue); + slot.RaiseValueChanged(_owner._owner, entry.property, oldValue, newValue); // During batch update values can't be removed immediately because they're needed to raise // the _sink.ValueChanged notification. They instead mark themselves for removal by setting diff --git a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs index 0caa984a22..aa5993f3b2 100644 --- a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs +++ b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs @@ -10,8 +10,8 @@ namespace Avalonia.Base.UnitTests { public class PriorityValueTests { - private static readonly IValueSink NullSink = new MockSink(); - private static readonly IAvaloniaObject Owner = Mock.Of(); + private static readonly AvaloniaObject Owner = new AvaloniaObject(); + private static readonly ValueStore ValueStore = new ValueStore(Owner); private static readonly StyledProperty TestProperty = new StyledProperty( "Test", typeof(PriorityValueTests), @@ -23,12 +23,12 @@ namespace Avalonia.Base.UnitTests var target = new PriorityValue( Owner, TestProperty, - NullSink, + ValueStore, new ConstantValueEntry( TestProperty, "1", BindingPriority.StyleTrigger, - NullSink)); + new(ValueStore))); Assert.Equal("1", target.GetValue().Value); Assert.Equal(BindingPriority.StyleTrigger, target.Priority); @@ -40,7 +40,7 @@ namespace Avalonia.Base.UnitTests var target = new PriorityValue( Owner, TestProperty, - NullSink); + ValueStore); target.SetValue("animation", BindingPriority.Animation); target.SetValue("local", BindingPriority.LocalValue); @@ -60,7 +60,7 @@ namespace Avalonia.Base.UnitTests var target = new PriorityValue( Owner, TestProperty, - NullSink); + ValueStore); target.SetValue("1", BindingPriority.LocalValue); target.SetValue("2", BindingPriority.LocalValue); @@ -74,7 +74,7 @@ namespace Avalonia.Base.UnitTests var target = new PriorityValue( Owner, TestProperty, - NullSink); + ValueStore); target.SetValue("1", BindingPriority.Style); target.SetValue("2", BindingPriority.Animation); @@ -93,7 +93,7 @@ namespace Avalonia.Base.UnitTests var target = new PriorityValue( Owner, TestProperty, - NullSink); + ValueStore); Assert.Equal(BindingPriority.Unset, target.Priority); target.SetValue("style", BindingPriority.Style); @@ -109,7 +109,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Binding_With_Same_Priority_Should_Be_Appended() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); var source2 = new Source("2"); @@ -129,7 +129,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Binding_With_Higher_Priority_Should_Be_Appended() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); var source2 = new Source("2"); @@ -149,7 +149,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Binding_With_Lower_Priority_Should_Be_Prepended() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); var source2 = new Source("2"); @@ -169,7 +169,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Second_Binding_With_Lower_Priority_Should_Be_Inserted_In_Middle() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); var source2 = new Source("2"); var source3 = new Source("3"); @@ -191,7 +191,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Competed_Binding_Should_Be_Removed() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); var source2 = new Source("2"); var source3 = new Source("3"); @@ -214,7 +214,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Value_Should_Come_From_Last_Entry() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); var source2 = new Source("2"); var source3 = new Source("3"); @@ -229,7 +229,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void LocalValue_Should_Override_LocalValue_Binding() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); target.AddBinding(source1, BindingPriority.LocalValue).Start(); @@ -241,7 +241,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void LocalValue_Should_Override_Style_Binding() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); target.AddBinding(source1, BindingPriority.Style).Start(); @@ -253,7 +253,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void LocalValue_Should_Not_Override_Animation_Binding() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); target.AddBinding(source1, BindingPriority.Animation).Start(); @@ -265,7 +265,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void NonAnimated_Value_Should_Be_Correct_1() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); var source2 = new Source("2"); var source3 = new Source("3"); @@ -281,7 +281,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void NonAnimated_Value_Should_Be_Correct_2() { - var target = new PriorityValue(Owner, TestProperty, NullSink); + var target = new PriorityValue(Owner, TestProperty, ValueStore); var source1 = new Source("1"); var source2 = new Source("2"); var source3 = new Source("3"); @@ -310,16 +310,5 @@ namespace Avalonia.Base.UnitTests public void OnCompleted() => _observer.OnCompleted(); } - - private class MockSink : IValueSink - { - public void Completed(StyledPropertyBase property, IPriorityValueEntry entry, Optional oldValue) - { - } - - public void ValueChanged(AvaloniaPropertyChangedEventArgs change) - { - } - } } }