Browse Source

Initial implementation of SetCurrentValue.

pull/10324/head
Steven Kirk 3 years ago
parent
commit
19078979e3
  1. 20
      src/Avalonia.Base/AvaloniaObject.cs
  2. 23
      src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
  3. 6
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  4. 29
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  5. 23
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  6. 270
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs

20
src/Avalonia.Base/AvaloniaObject.cs

@ -355,6 +355,23 @@ namespace Avalonia
SetDirectValueUnchecked(property, value);
}
public void SetCurrentValue<T>(StyledProperty<T> property, T value)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
LogPropertySet(property, value, BindingPriority.LocalValue);
if (value is UnsetValueType)
{
_values.ClearLocalValue(property);
}
else if (value is not DoNothingType)
{
_values.SetCurrentValue(property, value);
}
}
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
@ -547,7 +564,8 @@ namespace Avalonia
property,
GetValue(property),
BindingPriority.LocalValue,
null);
null,
false);
}
return _values.GetDiagnostic(property);

23
src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs

@ -3,28 +3,23 @@ using Avalonia.Data;
namespace Avalonia.Diagnostics
{
/// <summary>
/// Holds diagnostic-related information about the value of a <see cref="AvaloniaProperty"/>
/// on a <see cref="AvaloniaObject"/>.
/// Holds diagnostic-related information about the value of an <see cref="AvaloniaProperty"/>
/// on an <see cref="AvaloniaObject"/>.
/// </summary>
public class AvaloniaPropertyValue
{
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaPropertyValue"/> class.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The current property value.</param>
/// <param name="priority">The priority of the current value.</param>
/// <param name="diagnostic">A diagnostic string.</param>
public AvaloniaPropertyValue(
internal AvaloniaPropertyValue(
AvaloniaProperty property,
object? value,
BindingPriority priority,
string? diagnostic)
string? diagnostic,
bool isOverriddenCurrentValue)
{
Property = property;
Value = value;
Priority = priority;
Diagnostic = diagnostic;
IsOverriddenCurrentValue = isOverriddenCurrentValue;
}
/// <summary>
@ -46,5 +41,11 @@ namespace Avalonia.Diagnostics
/// Gets a diagnostic string.
/// </summary>
public string? Diagnostic { get; }
/// <summary>
/// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to
/// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
/// </summary>
public bool IsOverriddenCurrentValue { get; }
}
}

6
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@ -29,6 +29,12 @@ namespace Avalonia.PropertyStore
/// </summary>
public BindingPriority BasePriority { get; protected set; }
/// <summary>
/// Gets a value indicating whether the <see cref="Value"/> was overridden by a call to
/// <see cref="AvaloniaObject.SetCurrentValue{T}"/>.
/// </summary>
public bool IsOverridenCurrentValue { get; set; }
/// <summary>
/// Begins a reevaluation pass on the effective value.
/// </summary>

29
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -57,7 +57,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(priority != BindingPriority.LocalValue);
UpdateValueEntry(value, priority);
SetAndRaiseCore(owner, (StyledProperty<T>)value.Property, GetValue(value), priority);
SetAndRaiseCore(owner, (StyledProperty<T>)value.Property, GetValue(value), priority, false);
}
public void SetLocalValueAndRaise(
@ -65,7 +65,16 @@ namespace Avalonia.PropertyStore
StyledProperty<T> property,
T value)
{
SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue, false);
}
public void SetCurrentValueAndRaise(
ValueStore owner,
StyledProperty<T> property,
T value)
{
IsOverridenCurrentValue = true;
SetAndRaiseCore(owner, property, value, Priority, true);
}
public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
@ -98,7 +107,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(Priority != BindingPriority.Animation);
Debug.Assert(BasePriority != BindingPriority.Unset);
UpdateValueEntry(null, BindingPriority.Animation);
SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority);
SetAndRaiseCore(owner, (StyledProperty<T>)property, _baseValue!, BasePriority, false);
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
@ -158,15 +167,16 @@ namespace Avalonia.PropertyStore
ValueStore owner,
StyledProperty<T> property,
T value,
BindingPriority priority)
BindingPriority priority,
bool isOverriddenCurrentValue)
{
Debug.Assert(priority < BindingPriority.Inherited);
var oldValue = Value;
var valueChanged = false;
var baseValueChanged = false;
var v = value;
IsOverridenCurrentValue = isOverriddenCurrentValue;
if (_uncommon?._coerce is { } coerce)
v = coerce(owner.Owner, value);
@ -209,7 +219,6 @@ namespace Avalonia.PropertyStore
T baseValue,
BindingPriority basePriority)
{
Debug.Assert(priority < BindingPriority.Inherited);
Debug.Assert(basePriority > BindingPriority.Animation);
Debug.Assert(priority <= basePriority);
@ -225,7 +234,7 @@ namespace Avalonia.PropertyStore
bv = coerce(owner.Owner, baseValue);
}
if (priority != BindingPriority.Unset && !EqualityComparer<T>.Default.Equals(Value, v))
if (!EqualityComparer<T>.Default.Equals(Value, v))
{
Value = v;
valueChanged = true;
@ -233,9 +242,7 @@ namespace Avalonia.PropertyStore
_uncommon._uncoercedValue = value;
}
if (priority != BindingPriority.Unset &&
(BasePriority == BindingPriority.Unset ||
!EqualityComparer<T>.Default.Equals(_baseValue, bv)))
if (!EqualityComparer<T>.Default.Equals(_baseValue, bv))
{
_baseValue = v;
baseValueChanged = true;

23
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -7,7 +7,6 @@ using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Styling;
using Avalonia.Utilities;
using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
@ -159,8 +158,9 @@ namespace Avalonia.PropertyStore
public void ClearLocalValue(AvaloniaProperty property)
{
if (TryGetEffectiveValue(property, out var effective) &&
effective.Priority == BindingPriority.LocalValue)
(effective.Priority == BindingPriority.LocalValue || effective.IsOverridenCurrentValue))
{
effective.IsOverridenCurrentValue = false;
ReevaluateEffectiveValue(property, effective, ignoreLocalValue: true);
}
}
@ -209,6 +209,20 @@ namespace Avalonia.PropertyStore
}
}
public void SetCurrentValue<T>(StyledProperty<T> property, T value)
{
if (_effectiveValues.TryGetValue(property, out var v))
{
((EffectiveValue<T>)v).SetCurrentValueAndRaise(this, property, value);
}
else
{
var effectiveValue = new EffectiveValue<T>(Owner, property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetCurrentValueAndRaise(this, property, value);
}
}
public object? GetValue(AvaloniaProperty property)
{
if (_effectiveValues.TryGetValue(property, out var v))
@ -616,11 +630,13 @@ namespace Avalonia.PropertyStore
{
object? value;
BindingPriority priority;
bool overridden = false;
if (_effectiveValues.TryGetValue(property, out var v))
{
value = v.Value;
priority = v.Priority;
overridden = v.IsOverridenCurrentValue;
}
else if (property.Inherits && TryGetInheritedValue(property, out v))
{
@ -637,7 +653,8 @@ namespace Avalonia.PropertyStore
property,
value,
priority,
null);
null,
overridden);
}
private int InsertFrame(ValueFrame frame)

270
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs

@ -0,0 +1,270 @@
using System;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Reactive;
using Xunit;
using Observable = Avalonia.Reactive.Observable;
namespace Avalonia.Base.UnitTests
{
public class AvaloniaObjectTests_SetCurrentValue
{
[Fact]
public void SetCurrentValue_Sets_Unset_Value()
{
var target = new Class1();
target.SetCurrentValue(Class1.FooProperty, "newvalue");
Assert.Equal("newvalue", target.GetValue(Class1.FooProperty));
Assert.Equal(BindingPriority.Unset, GetPriority(target, Class1.FooProperty));
Assert.True(IsOverridden(target, Class1.FooProperty));
}
[Theory]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
[InlineData(BindingPriority.Animation)]
public void SetCurrentValue_Overrides_Existing_Value(BindingPriority priority)
{
var target = new Class1();
target.SetValue(Class1.FooProperty, "oldvalue", priority);
target.SetCurrentValue(Class1.FooProperty, "newvalue");
Assert.Equal("newvalue", target.GetValue(Class1.FooProperty));
Assert.Equal(priority, GetPriority(target, Class1.FooProperty));
Assert.True(IsOverridden(target, Class1.FooProperty));
}
[Fact]
public void SetCurrentValue_Overrides_Inherited_Value()
{
var parent = new Class1();
var target = new Class1 { InheritanceParent = parent };
parent.SetValue(Class1.InheritedProperty, "inheritedvalue");
target.SetCurrentValue(Class1.InheritedProperty, "newvalue");
Assert.Equal("newvalue", target.GetValue(Class1.InheritedProperty));
Assert.Equal(BindingPriority.Unset, GetPriority(target, Class1.InheritedProperty));
Assert.True(IsOverridden(target, Class1.InheritedProperty));
}
[Fact]
public void SetCurrentValue_Is_Inherited()
{
var parent = new Class1();
var target = new Class1 { InheritanceParent = parent };
parent.SetCurrentValue(Class1.InheritedProperty, "newvalue");
Assert.Equal("newvalue", target.GetValue(Class1.InheritedProperty));
Assert.Equal(BindingPriority.Inherited, GetPriority(target, Class1.InheritedProperty));
Assert.False(IsOverridden(target, Class1.InheritedProperty));
}
[Fact]
public void ClearValue_Clears_CurrentValue_With_Unset_Priority()
{
var target = new Class1();
target.SetCurrentValue(Class1.FooProperty, "newvalue");
target.ClearValue(Class1.FooProperty);
Assert.Equal("foodefault", target.Foo);
Assert.False(IsOverridden(target, Class1.FooProperty));
}
[Fact]
public void ClearValue_Clears_CurrentValue_With_Inherited_Priority()
{
var parent = new Class1();
var target = new Class1 { InheritanceParent = parent };
parent.SetValue(Class1.InheritedProperty, "inheritedvalue");
target.SetCurrentValue(Class1.InheritedProperty, "newvalue");
target.ClearValue(Class1.InheritedProperty);
Assert.Equal("inheritedvalue", target.Inherited);
Assert.False(IsOverridden(target, Class1.FooProperty));
}
[Fact]
public void ClearValue_Clears_CurrentValue_With_LocalValue_Priority()
{
var target = new Class1();
target.SetValue(Class1.FooProperty, "localvalue");
target.SetCurrentValue(Class1.FooProperty, "newvalue");
target.ClearValue(Class1.FooProperty);
Assert.Equal("foodefault", target.Foo);
Assert.False(IsOverridden(target, Class1.FooProperty));
}
[Fact]
public void ClearValue_Clears_CurrentValue_With_Style_Priority()
{
var target = new Class1();
target.SetValue(Class1.FooProperty, "stylevalue", BindingPriority.Style);
target.SetCurrentValue(Class1.FooProperty, "newvalue");
target.ClearValue(Class1.FooProperty);
Assert.Equal("stylevalue", target.Foo);
Assert.False(IsOverridden(target, Class1.FooProperty));
}
[Fact]
public void SetCurrentValue_Can_Be_Coerced()
{
var target = new Class1();
target.SetCurrentValue(Class1.CoercedProperty, 60);
Assert.Equal(60, target.GetValue(Class1.CoercedProperty));
target.CoerceMax = 50;
target.CoerceValue(Class1.CoercedProperty);
Assert.Equal(50, target.GetValue(Class1.CoercedProperty));
target.CoerceMax = 100;
target.CoerceValue(Class1.CoercedProperty);
Assert.Equal(60, target.GetValue(Class1.CoercedProperty));
}
[Theory]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
[InlineData(BindingPriority.Animation)]
public void SetValue_Overrides_CurrentValue_With_Unset_Priority(BindingPriority priority)
{
var target = new Class1();
target.SetCurrentValue(Class1.FooProperty, "current");
target.SetValue(Class1.FooProperty, "setvalue", priority);
Assert.Equal("setvalue", target.Foo);
Assert.Equal(priority, GetPriority(target, Class1.FooProperty));
Assert.False(IsOverridden(target, Class1.FooProperty));
}
[Fact]
public void Animation_Value_Overrides_CurrentValue_With_LocalValue_Priority()
{
var target = new Class1();
target.SetValue(Class1.FooProperty, "localvalue");
target.SetCurrentValue(Class1.FooProperty, "current");
target.SetValue(Class1.FooProperty, "setvalue", BindingPriority.Animation);
Assert.Equal("setvalue", target.Foo);
Assert.Equal(BindingPriority.Animation, GetPriority(target, Class1.FooProperty));
Assert.False(IsOverridden(target, Class1.FooProperty));
}
[Fact]
public void StyleTrigger_Value_Overrides_CurrentValue_With_Style_Priority()
{
var target = new Class1();
target.SetValue(Class1.FooProperty, "style", BindingPriority.Style);
target.SetCurrentValue(Class1.FooProperty, "current");
target.SetValue(Class1.FooProperty, "setvalue", BindingPriority.StyleTrigger);
Assert.Equal("setvalue", target.Foo);
Assert.Equal(BindingPriority.StyleTrigger, GetPriority(target, Class1.FooProperty));
Assert.False(IsOverridden(target, Class1.FooProperty));
}
[Theory]
[InlineData(BindingPriority.LocalValue)]
[InlineData(BindingPriority.Style)]
[InlineData(BindingPriority.Animation)]
public void Binding_Overrides_CurrentValue_With_Unset_Priority(BindingPriority priority)
{
var target = new Class1();
target.SetCurrentValue(Class1.FooProperty, "current");
var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), priority);
Assert.Equal("binding", target.Foo);
Assert.Equal(priority, GetPriority(target, Class1.FooProperty));
Assert.False(IsOverridden(target, Class1.FooProperty));
s.Dispose();
Assert.Equal("foodefault", target.Foo);
}
[Fact]
public void Animation_Binding_Overrides_CurrentValue_With_LocalValue_Priority()
{
var target = new Class1();
target.SetValue(Class1.FooProperty, "localvalue");
target.SetCurrentValue(Class1.FooProperty, "current");
var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), BindingPriority.Animation);
Assert.Equal("binding", target.Foo);
Assert.Equal(BindingPriority.Animation, GetPriority(target, Class1.FooProperty));
Assert.False(IsOverridden(target, Class1.FooProperty));
s.Dispose();
Assert.Equal("current", target.Foo);
}
[Fact]
public void StyleTrigger_Binding_Overrides_CurrentValue_With_Style_Priority()
{
var target = new Class1();
target.SetValue(Class1.FooProperty, "style", BindingPriority.Style);
target.SetCurrentValue(Class1.FooProperty, "current");
var s = target.Bind(Class1.FooProperty, Observable.SingleValue("binding"), BindingPriority.StyleTrigger);
Assert.Equal("binding", target.Foo);
Assert.Equal(BindingPriority.StyleTrigger, GetPriority(target, Class1.FooProperty));
Assert.False(IsOverridden(target, Class1.FooProperty));
s.Dispose();
Assert.Equal("style", target.Foo);
}
private BindingPriority GetPriority(AvaloniaObject target, AvaloniaProperty property)
{
return target.GetDiagnostic(property).Priority;
}
private bool IsOverridden(AvaloniaObject target, AvaloniaProperty property)
{
return target.GetDiagnostic(property).IsOverriddenCurrentValue;
}
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<string> FooProperty =
AvaloniaProperty.Register<Class1, string>(nameof(Foo), "foodefault");
public static readonly StyledProperty<string> InheritedProperty =
AvaloniaProperty.Register<Class1, string>(nameof(Inherited), "inheriteddefault", inherits: true);
public static readonly StyledProperty<double> CoercedProperty =
AvaloniaProperty.Register<Class1, double>(nameof(Coerced), coerce: Coerce);
public string Foo => GetValue(FooProperty);
public string Inherited => GetValue(InheritedProperty);
public double Coerced => GetValue(CoercedProperty);
public double CoerceMax { get; set; } = 100;
private static double Coerce(AvaloniaObject sender, double value)
{
return Math.Min(value, ((Class1)sender).CoerceMax);
}
}
}
}
Loading…
Cancel
Save