Skip to content

Commit

Permalink
Merge branch 'master' into fixes/x11window-nre
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkatz6 authored Mar 8, 2023
2 parents 1143b33 + e3d94db commit f55e7af
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 61 deletions.
5 changes: 3 additions & 2 deletions Avalonia.Desktop.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
"samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
Expand Down Expand Up @@ -41,6 +41,7 @@
"src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj",
"src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
"src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj",
"src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj",
Expand All @@ -63,4 +64,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
}
}
65 changes: 27 additions & 38 deletions src/Avalonia.Base/AvaloniaProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.Utilities;

namespace Avalonia
Expand All @@ -20,12 +19,20 @@ public abstract class AvaloniaProperty : IEquatable<AvaloniaProperty>, IProperty
public static readonly object UnsetValue = new UnsetValueType();

private static int s_nextId;

/// <summary>
/// Provides a metadata object for types which have no metadata of their own.
/// </summary>
private readonly AvaloniaPropertyMetadata _defaultMetadata;

/// <summary>
/// Provides a fast path when the property has no metadata overrides.
/// </summary>
private KeyValuePair<Type, AvaloniaPropertyMetadata>? _singleMetadata;

private readonly Dictionary<Type, AvaloniaPropertyMetadata> _metadata;
private readonly Dictionary<Type, AvaloniaPropertyMetadata> _metadataCache = new Dictionary<Type, AvaloniaPropertyMetadata>();

private bool _hasMetadataOverrides;

/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
/// </summary>
Expand Down Expand Up @@ -57,7 +64,8 @@ protected AvaloniaProperty(
Id = s_nextId++;

_metadata.Add(ownerType, metadata ?? throw new ArgumentNullException(nameof(metadata)));
_defaultMetadata = metadata;
_defaultMetadata = metadata.GenerateTypeSafeMetadata();
_singleMetadata = new(ownerType, metadata);
}

/// <summary>
Expand All @@ -80,9 +88,6 @@ protected AvaloniaProperty(
Id = source.Id;
_defaultMetadata = source._defaultMetadata;

// Properties that have different owner can't use fast path for metadata.
_hasMetadataOverrides = true;

if (metadata != null)
{
_metadata.Add(ownerType, metadata);
Expand Down Expand Up @@ -453,33 +458,14 @@ public override int GetHashCode()
}

/// <summary>
/// Gets the property metadata for the specified type.
/// Gets the <see cref="AvaloniaPropertyMetadata"/> which applies to this property when it is used with the specified type.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <returns>
/// The property metadata.
/// </returns>
public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject
{
return GetMetadata(typeof(T));
}
/// <typeparam name="T">The type for which to retrieve metadata.</typeparam>
public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject => GetMetadata(typeof(T));

/// <summary>
/// Gets the property metadata for the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// The property metadata.
/// </returns>
public AvaloniaPropertyMetadata GetMetadata(Type type)
{
if (!_hasMetadataOverrides)
{
return _defaultMetadata;
}

return GetMetadataWithOverrides(type);
}
/// <inheritdoc cref="GetMetadata{T}"/>
/// <param name="type">The type for which to retrieve metadata.</param>
public AvaloniaPropertyMetadata GetMetadata(Type type) => GetMetadataWithOverrides(type);

/// <summary>
/// Checks whether the <paramref name="value"/> is valid for the property.
Expand Down Expand Up @@ -578,7 +564,7 @@ protected void OverrideMetadata(Type type, AvaloniaPropertyMetadata metadata)
_metadata.Add(type, metadata);
_metadataCache.Clear();

_hasMetadataOverrides = true;
_singleMetadata = null;
}

protected abstract IObservable<AvaloniaPropertyChangedEventArgs> GetChanged();
Expand All @@ -595,7 +581,12 @@ private AvaloniaPropertyMetadata GetMetadataWithOverrides(Type type)
return result;
}

Type? currentType = type;
if (_singleMetadata is { } singleMetadata)
{
return _metadataCache[type] = singleMetadata.Key.IsAssignableFrom(type) ? singleMetadata.Value : _defaultMetadata;
}

var currentType = type;

while (currentType != null)
{
Expand All @@ -609,13 +600,11 @@ private AvaloniaPropertyMetadata GetMetadataWithOverrides(Type type)
currentType = currentType.BaseType;
}

_metadataCache[type] = _defaultMetadata;

return _defaultMetadata;
return _metadataCache[type] = _defaultMetadata;
}

bool IPropertyInfo.CanGet => true;
bool IPropertyInfo.CanSet => true;
bool IPropertyInfo.CanSet => !IsReadOnly;
object? IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
void IPropertyInfo.Set(object target, object? value) => ((AvaloniaObject)target).SetValue(this, value);
}
Expand Down
10 changes: 9 additions & 1 deletion src/Avalonia.Base/AvaloniaPropertyMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Avalonia
/// <summary>
/// Base class for avalonia property metadata.
/// </summary>
public class AvaloniaPropertyMetadata
public abstract class AvaloniaPropertyMetadata
{
private BindingMode _defaultBindingMode;

Expand Down Expand Up @@ -61,5 +61,13 @@ public virtual void Merge(

EnableDataValidation ??= baseMetadata.EnableDataValidation;
}

/// <summary>
/// Gets a copy of this object configured for use with any owner type.
/// </summary>
/// <remarks>
/// For example, delegates which receive the owner object should be removed.
/// </remarks>
public abstract AvaloniaPropertyMetadata GenerateTypeSafeMetadata();
}
}
2 changes: 2 additions & 0 deletions src/Avalonia.Base/DirectPropertyMetadata`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ public override void Merge(AvaloniaPropertyMetadata baseMetadata, AvaloniaProper
UnsetValue ??= src.UnsetValue;
}
}

public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() => new DirectPropertyMetadata<TValue>(UnsetValue, DefaultBindingMode, EnableDataValidation);
}
}
7 changes: 5 additions & 2 deletions src/Avalonia.Base/StyledProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ public class StyledProperty<TValue> : AvaloniaProperty<TValue>, IStyledPropertyA
/// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="metadata">The property metadata.</param>
/// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="validate">
/// <para>A method which returns "false" for values that are never valid for this property.</para>
/// <para>This method is not part of the property's metadata and so cannot be changed after registration.</para>
/// </param>
/// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param>
public StyledProperty(
string name,
Expand All @@ -41,7 +44,7 @@ public StyledProperty(
}

/// <summary>
/// Gets the value validation callback for the property.
/// A method which returns "false" for values that are never valid for this property.
/// </summary>
public Func<TValue, bool>? ValidateValue { get; }

Expand Down
2 changes: 2 additions & 0 deletions src/Avalonia.Base/StyledPropertyMetadata`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,7 @@ public override void Merge(AvaloniaPropertyMetadata baseMetadata, AvaloniaProper
}
}
}

public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() => new StyledPropertyMetadata<TValue>(DefaultValue, DefaultBindingMode, enableDataValidation: EnableDataValidation ?? false);
}
}
9 changes: 5 additions & 4 deletions src/Avalonia.Controls/ToolTipService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ internal void TipChanged(AvaloniaPropertyChangedEventArgs e)
{
Close(control);
}
else
else
{
var tip = control.GetValue(ToolTip.ToolTipProperty);

tip!.Content = e.NewValue;
if (control.GetValue(ToolTip.ToolTipProperty) is { } tip)
{
tip.Content = e.NewValue;
}
}
}
}
Expand Down
46 changes: 32 additions & 14 deletions tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.Utilities;
using Xunit;

namespace Avalonia.Base.UnitTests
Expand All @@ -29,7 +27,7 @@ public void Name_Cannot_Contain_Periods()
[Fact]
public void GetMetadata_Returns_Supplied_Value()
{
var metadata = new AvaloniaPropertyMetadata();
var metadata = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);

Assert.Same(metadata, target.GetMetadata<Class1>());
Expand All @@ -38,26 +36,30 @@ public void GetMetadata_Returns_Supplied_Value()
[Fact]
public void GetMetadata_Returns_Supplied_Value_For_Derived_Class()
{
var metadata = new AvaloniaPropertyMetadata();
var metadata = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);

Assert.Same(metadata, target.GetMetadata<Class2>());
}

[Fact]
public void GetMetadata_Returns_Supplied_Value_For_Unrelated_Class()
public void GetMetadata_Returns_TypeSafe_Metadata_For_Unrelated_Class()
{
var metadata = new AvaloniaPropertyMetadata();
var metadata = new TestMetadata(BindingMode.OneWayToSource, true, x => { _ = (StyledElement)x; });
var target = new TestProperty<string>("test", typeof(Class3), metadata);

Assert.Same(metadata, target.GetMetadata<Class2>());
var targetMetadata = (TestMetadata)target.GetMetadata<Class2>();

Assert.Equal(metadata.DefaultBindingMode, targetMetadata.DefaultBindingMode);
Assert.Equal(metadata.EnableDataValidation, targetMetadata.EnableDataValidation);
Assert.Equal(null, targetMetadata.OwnerSpecificAction);
}

[Fact]
public void GetMetadata_Returns_Overridden_Value()
{
var metadata = new AvaloniaPropertyMetadata();
var overridden = new AvaloniaPropertyMetadata();
var metadata = new TestMetadata();
var overridden = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);

target.OverrideMetadata<Class2>(overridden);
Expand All @@ -68,9 +70,9 @@ public void GetMetadata_Returns_Overridden_Value()
[Fact]
public void OverrideMetadata_Should_Merge_Values()
{
var metadata = new AvaloniaPropertyMetadata(BindingMode.TwoWay);
var metadata = new TestMetadata(BindingMode.TwoWay);
var notify = (Action<AvaloniaObject, bool>)((a, b) => { });
var overridden = new AvaloniaPropertyMetadata();
var overridden = new TestMetadata();
var target = new TestProperty<string>("test", typeof(Class1), metadata);

target.OverrideMetadata<Class2>(overridden);
Expand Down Expand Up @@ -131,15 +133,31 @@ public void Property_Equals_Should_Handle_Null()
[Fact]
public void PropertyMetadata_BindingMode_Default_Returns_OneWay()
{
var data = new AvaloniaPropertyMetadata(defaultBindingMode: BindingMode.Default);
var data = new TestMetadata(defaultBindingMode: BindingMode.Default);

Assert.Equal(BindingMode.OneWay, data.DefaultBindingMode);
}

private class TestMetadata : AvaloniaPropertyMetadata
{
public Action<AvaloniaObject> OwnerSpecificAction { get; }

public TestMetadata(BindingMode defaultBindingMode = BindingMode.Default,
bool? enableDataValidation = null,
Action<AvaloniaObject> ownerSpecificAction = null)
: base(defaultBindingMode, enableDataValidation)
{
OwnerSpecificAction = ownerSpecificAction;
}

public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() =>
new TestMetadata(DefaultBindingMode, EnableDataValidation, null);
}

private class TestProperty<TValue> : AvaloniaProperty<TValue>
{
public TestProperty(string name, Type ownerType, AvaloniaPropertyMetadata metadata = null)
: base(name, ownerType, metadata ?? new AvaloniaPropertyMetadata())
public TestProperty(string name, Type ownerType, TestMetadata metadata = null)
: base(name, ownerType, metadata ?? new TestMetadata())
{
}

Expand Down

0 comments on commit f55e7af

Please sign in to comment.