From b3af7f0c6a012512520a09ac06a39fa5fc1b24c0 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Wed, 4 May 2022 14:45:02 +0200 Subject: [PATCH 01/25] Proper Canvas positioning for adorner layer --- src/Avalonia.Controls/Canvas.cs | 75 +++++++++++-------- .../Primitives/AdornerLayer.cs | 2 +- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/Avalonia.Controls/Canvas.cs b/src/Avalonia.Controls/Canvas.cs index fabf8978c7..adee7d4d90 100644 --- a/src/Avalonia.Controls/Canvas.cs +++ b/src/Avalonia.Controls/Canvas.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Concurrency; using Avalonia.Input; using Avalonia.Layout; @@ -159,47 +160,57 @@ namespace Avalonia.Controls } /// - /// Arranges the control's children. + /// Arranges a single child. /// - /// The size allocated to the control. - /// The space taken. - protected override Size ArrangeOverride(Size finalSize) + /// The child to arrange. + /// The size allocated to the canvas. + protected virtual void ArrangeChild(Control child, Size finalSize) { - foreach (Control child in Children) - { - double x = 0.0; - double y = 0.0; - double elementLeft = GetLeft(child); + double x = 0.0; + double y = 0.0; + double elementLeft = GetLeft(child); - if (!double.IsNaN(elementLeft)) - { - x = elementLeft; - } - else + if (!double.IsNaN(elementLeft)) + { + x = elementLeft; + } + else + { + // Arrange with right. + double elementRight = GetRight(child); + if (!double.IsNaN(elementRight)) { - // Arrange with right. - double elementRight = GetRight(child); - if (!double.IsNaN(elementRight)) - { - x = finalSize.Width - child.DesiredSize.Width - elementRight; - } + x = finalSize.Width - child.DesiredSize.Width - elementRight; } + } - double elementTop = GetTop(child); - if (!double.IsNaN(elementTop) ) - { - y = elementTop; - } - else + double elementTop = GetTop(child); + if (!double.IsNaN(elementTop)) + { + y = elementTop; + } + else + { + double elementBottom = GetBottom(child); + if (!double.IsNaN(elementBottom)) { - double elementBottom = GetBottom(child); - if (!double.IsNaN(elementBottom)) - { - y = finalSize.Height - child.DesiredSize.Height - elementBottom; - } + y = finalSize.Height - child.DesiredSize.Height - elementBottom; } + } - child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); + child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); + } + + /// + /// Arranges the control's children. + /// + /// The size allocated to the control. + /// The space taken. + protected override Size ArrangeOverride(Size finalSize) + { + foreach (Control child in Children) + { + ArrangeChild(child, finalSize); } return finalSize; diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index fb047d93df..5ad4e39baf 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -105,7 +105,7 @@ namespace Avalonia.Controls.Primitives } else { - child.Arrange(new Rect(finalSize)); + ArrangeChild((Control) child, finalSize); } } } From a5b2eb08d4000d7bb4c8c4d1f21afe51bb2031b9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 May 2022 21:12:25 +0300 Subject: [PATCH 02/25] Added support for IReflectableType in InpcPropertyAccessorPlugin --- .../Plugins/InpcPropertyAccessorPlugin.cs | 19 +- .../Data/BindingTests.cs | 19 ++ .../Data/DynamicReflectableType.cs | 221 ++++++++++++++++++ 3 files changed, 252 insertions(+), 7 deletions(-) create mode 100644 tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 33cecd10a7..b93bf87fdf 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -17,7 +17,7 @@ namespace Avalonia.Data.Core.Plugins new Dictionary<(Type, string), PropertyInfo?>(); /// - public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null; + public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null; /// /// Starts monitoring the value of a property on an object. @@ -36,7 +36,7 @@ namespace Avalonia.Data.Core.Plugins if (!reference.TryGetTarget(out var instance) || instance is null) return null; - var p = GetFirstPropertyWithName(instance.GetType(), propertyName); + var p = GetFirstPropertyWithName(instance, propertyName); if (p != null) { @@ -50,8 +50,16 @@ namespace Avalonia.Data.Core.Plugins } } - private PropertyInfo? GetFirstPropertyWithName(Type type, string propertyName) + private const BindingFlags PropertyBindingFlags = + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName) { + if (instance is IReflectableType reflectableType) + return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags); + + var type = instance.GetType(); + var key = (type, propertyName); if (!_propertyLookup.TryGetValue(key, out var propertyInfo)) @@ -66,10 +74,7 @@ namespace Avalonia.Data.Core.Plugins { PropertyInfo? found = null; - const BindingFlags bindingFlags = - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; - - var properties = type.GetProperties(bindingFlags); + var properties = type.GetProperties(PropertyBindingFlags); foreach (PropertyInfo propertyInfo in properties) { diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 055de999e2..11a22f0dec 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -617,6 +617,25 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal(0, source.SubscriberCount); } + + [Fact] + public void Binding_Can_Resolve_Property_From_IReflectableType_Type() + { + var source = new DynamicReflectableType { ["Foo"] = "foo" }; + var target = new TwoWayBindingTest { DataContext = source }; + var binding = new Binding + { + Path = "Foo", + }; + + target.Bind(TwoWayBindingTest.TwoWayProperty, binding); + + Assert.Equal("foo", target.TwoWay); + source["Foo"] = "bar"; + Assert.Equal("bar", target.TwoWay); + target.TwoWay = "baz"; + Assert.Equal("baz", source["Foo"]); + } private class StyledPropertyClass : AvaloniaObject { diff --git a/tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs b/tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs new file mode 100644 index 0000000000..3c57bd38cf --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using Moq; + +namespace Avalonia.Markup.UnitTests.Data; + +class DynamicReflectableType : IReflectableType, INotifyPropertyChanged, IEnumerable> +{ + private Dictionary _dic = new(); + + public TypeInfo GetTypeInfo() + { + return new FakeTypeInfo(); + } + + public void Add(string key, object value) + { + _dic.Add(key, value); + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key)); + } + + public object this[string key] + { + get => _dic[key]; + set + { + _dic[key] = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key)); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + public IEnumerator> GetEnumerator() + { + return _dic.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_dic).GetEnumerator(); + } + + + class FakeTypeInfo : TypeInfo + { + protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, + ParameterModifier[] modifiers) + { + var propInfo = new Mock(); + propInfo.SetupGet(x => x.Name).Returns(name); + propInfo.SetupGet(x => x.PropertyType).Returns(typeof(object)); + propInfo.SetupGet(x => x.CanWrite).Returns(true); + propInfo.Setup(x => x.GetValue(It.IsAny(), It.IsAny())) + .Returns((object target, object [] _) => ((DynamicReflectableType)target)._dic.GetValueOrDefault(name)); + propInfo.Setup(x => x.SetValue(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((object target, object value, object [] _) => + { + ((DynamicReflectableType)target)._dic[name] = value; + }); + return propInfo.Object; + } + + #region NotSupported + + + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotSupportedException(); + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotSupportedException(); + } + + public override Module Module { get; } + public override string Namespace { get; } + public override string Name { get; } + protected override TypeAttributes GetAttributeFlagsImpl() + { + throw new NotSupportedException(); + } + + protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, + Type[] types, ParameterModifier[] modifiers) + { + throw new NotSupportedException(); + } + + public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override Type GetElementType() + { + throw new NotSupportedException(); + } + + public override EventInfo GetEvent(string name, BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override EventInfo[] GetEvents(BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override FieldInfo GetField(string name, BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override MemberInfo[] GetMembers(BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, + Type[] types, ParameterModifier[] modifiers) + { + throw new NotSupportedException(); + } + + public override MethodInfo[] GetMethods(BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, + ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + throw new NotSupportedException(); + } + + public override Type UnderlyingSystemType { get; } + + protected override bool IsArrayImpl() + { + throw new NotSupportedException(); + } + + protected override bool IsByRefImpl() + { + throw new NotSupportedException(); + } + + protected override bool IsCOMObjectImpl() + { + throw new NotSupportedException(); + } + + protected override bool IsPointerImpl() + { + throw new NotSupportedException(); + } + + protected override bool IsPrimitiveImpl() + { + throw new NotSupportedException(); + } + + public override Assembly Assembly { get; } + public override string AssemblyQualifiedName { get; } + public override Type BaseType { get; } + public override string FullName { get; } + public override Guid GUID { get; } + + + + protected override bool HasElementTypeImpl() + { + throw new NotSupportedException(); + } + + public override Type GetNestedType(string name, BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override Type[] GetNestedTypes(BindingFlags bindingAttr) + { + throw new NotSupportedException(); + } + + public override Type GetInterface(string name, bool ignoreCase) + { + throw new NotSupportedException(); + } + + public override Type[] GetInterfaces() + { + throw new NotSupportedException(); + } + + + #endregion + + } +} \ No newline at end of file From 35db70c8d4905eba47f0fd73e2de0830d5e8e2fe Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 May 2022 14:37:23 +0200 Subject: [PATCH 03/25] Don't expose viewbox container as logical child. #7735 introduced an internal container control which hosts the child, but it exposed this child in the logical tree, breaking any styles which relied on the `Viewbox.Child` being the logical child of the `Viewbox`. Fix this by introducing a simple internal `ViewboxContainer` control as the container. --- src/Avalonia.Controls/Viewbox.cs | 43 +++++++++++++++++-- .../ViewboxTests.cs | 20 +++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index 33a05f126d..f3ec53ed2d 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -8,7 +8,7 @@ namespace Avalonia.Controls /// public class Viewbox : Control { - private Decorator _containerVisual; + private ViewboxContainer _containerVisual; /// /// Defines the property. @@ -37,9 +37,8 @@ namespace Avalonia.Controls public Viewbox() { - _containerVisual = new Decorator(); + _containerVisual = new ViewboxContainer(); _containerVisual.RenderTransformOrigin = RelativePoint.TopLeft; - LogicalChildren.Add(_containerVisual); VisualChildren.Add(_containerVisual); } @@ -88,7 +87,22 @@ namespace Avalonia.Controls if (change.Property == ChildProperty) { + var (oldChild, newChild) = change.GetOldAndNewValue(); + + if (oldChild is not null) + { + ((ISetLogicalParent)oldChild).SetParent(null); + LogicalChildren.Remove(oldChild); + } + _containerVisual.Child = change.GetNewValue(); + + if (newChild is not null) + { + ((ISetLogicalParent)newChild).SetParent(this); + LogicalChildren.Add(newChild); + } + InvalidateMeasure(); } } @@ -129,5 +143,28 @@ namespace Avalonia.Controls return finalSize; } + + private class ViewboxContainer : Control + { + private IControl? _child; + + public IControl? Child + { + get => _child; + set + { + if (_child != value) + { + if (_child is not null) + VisualChildren.Remove(_child); + + _child = value; + + if (_child is not null) + VisualChildren.Add(_child); + } + } + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs index d33e55341b..39a14a6a7e 100644 --- a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Shapes; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -170,5 +171,24 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(expectedScale, scaleTransform.ScaleX); Assert.Equal(expectedScale, scaleTransform.ScaleY); } + + [Fact] + public void Child_Should_Be_Logical_Child_Of_Viewbox() + { + var target = new Viewbox(); + + Assert.Empty(target.GetLogicalChildren()); + + var child = new Canvas(); + target.Child = child; + + Assert.Single(target.GetLogicalChildren(), child); + Assert.Same(child.GetLogicalParent(), target); + + target.Child = null; + + Assert.Empty(target.GetLogicalChildren()); + Assert.Null(child.GetLogicalParent()); + } } } From 36fefd871704a48ef6fd553845ad6c03e70ea9ad Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 May 2022 22:44:29 +0200 Subject: [PATCH 04/25] Fix copypasta. Co-authored-by: Max Katz --- src/Avalonia.Controls/Viewbox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index f3ec53ed2d..55c52d8ed9 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -95,7 +95,7 @@ namespace Avalonia.Controls LogicalChildren.Remove(oldChild); } - _containerVisual.Child = change.GetNewValue(); + _containerVisual.Child = newChild; if (newChild is not null) { From ccce304c69385fc3f5c41a3646084792c0f37f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 13 May 2022 14:13:20 +0200 Subject: [PATCH 05/25] Fix TimePicker property registrations --- src/Avalonia.Controls/DateTimePickers/TimePicker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index f04c79505e..047667567d 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -38,13 +38,13 @@ namespace Avalonia.Controls /// Defines the property /// public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); + AvaloniaProperty.Register(nameof(Header)); /// /// Defines the property /// public static readonly StyledProperty HeaderTemplateProperty = - AvaloniaProperty.Register(nameof(HeaderTemplate)); + AvaloniaProperty.Register(nameof(HeaderTemplate)); /// /// Defines the property From 2ee94118305364b72aee6865d427f1330314bc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 13 May 2022 14:16:05 +0200 Subject: [PATCH 06/25] Fix SelectingItemsControl WrapSelection property owner --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 164aab32db..a730659330 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -118,7 +118,7 @@ namespace Avalonia.Controls.Primitives /// Defines the property. /// public static readonly StyledProperty WrapSelectionProperty = - AvaloniaProperty.Register(nameof(WrapSelection), defaultValue: false); + AvaloniaProperty.Register(nameof(WrapSelection), defaultValue: false); private static readonly IList Empty = Array.Empty(); private string _textSearchTerm = string.Empty; From 08cbec6f23848dab0680531a059eb8c92babb290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Fri, 13 May 2022 14:28:42 +0200 Subject: [PATCH 07/25] Fix ContentPresenter property field types --- .../Presenters/ContentPresenter.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 996cb29534..12a8dd747d 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -52,67 +52,67 @@ namespace Avalonia.Controls.Presenters /// /// Defines the property. /// - public static readonly AttachedProperty ForegroundProperty = + public static readonly StyledProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontFamilyProperty = + public static readonly StyledProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontSizeProperty = + public static readonly StyledProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontStyleProperty = + public static readonly StyledProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontWeightProperty = + public static readonly StyledProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontStretchProperty = + public static readonly StyledProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty TextAlignmentProperty = + public static readonly StyledProperty TextAlignmentProperty = TextBlock.TextAlignmentProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty TextWrappingProperty = + public static readonly StyledProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty TextTrimmingProperty = + public static readonly StyledProperty TextTrimmingProperty = TextBlock.TextTrimmingProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty LineHeightProperty = + public static readonly StyledProperty LineHeightProperty = TextBlock.LineHeightProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty MaxLinesProperty = + public static readonly StyledProperty MaxLinesProperty = TextBlock.MaxLinesProperty.AddOwner(); /// From b34095fda37618cde431112f0abc3be3056f5ac2 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 13 May 2022 23:53:26 +0200 Subject: [PATCH 08/25] Optimize memory usage of styles and fix ordering issue in devtools. --- src/Avalonia.Base/Styling/IStyleInstance.cs | 5 +++ .../Styling/PropertySetterInstance.cs | 10 +++--- ...e.cs => PropertySetterTemplateInstance.cs} | 31 ++++++++-------- src/Avalonia.Base/Styling/Setter.cs | 9 ----- src/Avalonia.Base/Styling/StyleInstance.cs | 35 ++++++++++--------- .../ViewModels/ControlDetailsViewModel.cs | 3 +- 6 files changed, 46 insertions(+), 47 deletions(-) rename src/Avalonia.Base/Styling/{PropertySetterLazyInstance.cs => PropertySetterTemplateInstance.cs} (79%) diff --git a/src/Avalonia.Base/Styling/IStyleInstance.cs b/src/Avalonia.Base/Styling/IStyleInstance.cs index 8ddb989bc0..d4f7510eb3 100644 --- a/src/Avalonia.Base/Styling/IStyleInstance.cs +++ b/src/Avalonia.Base/Styling/IStyleInstance.cs @@ -14,6 +14,11 @@ namespace Avalonia.Styling /// IStyle Source { get; } + /// + /// Gets a value indicating whether this style has an activator. + /// + bool HasActivator { get; } + /// /// Gets a value indicating whether this style is active. /// diff --git a/src/Avalonia.Base/Styling/PropertySetterInstance.cs b/src/Avalonia.Base/Styling/PropertySetterInstance.cs index 48f462d006..c4e8f47e67 100644 --- a/src/Avalonia.Base/Styling/PropertySetterInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterInstance.cs @@ -44,7 +44,7 @@ namespace Avalonia.Styling { if (hasActivator) { - if (_styledProperty is object) + if (_styledProperty is not null) { _subscription = _target.Bind(_styledProperty, this, BindingPriority.StyleTrigger); } @@ -55,13 +55,15 @@ namespace Avalonia.Styling } else { - if (_styledProperty is object) + var target = (AvaloniaObject) _target; + + if (_styledProperty is not null) { - _subscription = _target.SetValue(_styledProperty!, _value, BindingPriority.Style); + _subscription = target.SetValue(_styledProperty!, _value, BindingPriority.Style); } else { - _target.SetValue(_directProperty!, _value); + target.SetValue(_directProperty!, _value); } } } diff --git a/src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs similarity index 79% rename from src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs rename to src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs index 92653d0064..0f6efef1be 100644 --- a/src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs @@ -11,42 +11,42 @@ namespace Avalonia.Styling /// evaluated. /// /// The target property type. - internal class PropertySetterLazyInstance : SingleSubscriberObservableBase>, + internal class PropertySetterTemplateInstance : SingleSubscriberObservableBase>, ISetterInstance { private readonly IStyleable _target; private readonly StyledPropertyBase? _styledProperty; private readonly DirectPropertyBase? _directProperty; - private readonly Func _valueFactory; + private readonly ITemplate _template; private BindingValue _value; private IDisposable? _subscription; private bool _isActive; - public PropertySetterLazyInstance( + public PropertySetterTemplateInstance( IStyleable target, StyledPropertyBase property, - Func valueFactory) + ITemplate template) { _target = target; _styledProperty = property; - _valueFactory = valueFactory; + _template = template; } - public PropertySetterLazyInstance( + public PropertySetterTemplateInstance( IStyleable target, DirectPropertyBase property, - Func valueFactory) + ITemplate template) { _target = target; _directProperty = property; - _valueFactory = valueFactory; + _template = template; } public void Start(bool hasActivator) { _isActive = !hasActivator; - if (_styledProperty is object) + if (_styledProperty is not null) { var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style; _subscription = _target.Bind(_styledProperty, this, priority); @@ -77,7 +77,7 @@ namespace Avalonia.Styling public override void Dispose() { - if (_subscription is object) + if (_subscription is not null) { var sub = _subscription; _subscription = null; @@ -85,7 +85,7 @@ namespace Avalonia.Styling } else if (_isActive) { - if (_styledProperty is object) + if (_styledProperty is not null) { _target.ClearValue(_styledProperty); } @@ -101,22 +101,21 @@ namespace Avalonia.Styling protected override void Subscribed() => PublishNext(); protected override void Unsubscribed() { } - private T GetValue() + private void EnsureTemplate() { if (_value.HasValue) { - return _value.Value; + return; } - _value = _valueFactory(); - return _value.Value; + _value = (T) _template.Build(); } private void PublishNext() { if (_isActive) { - GetValue(); + EnsureTemplate(); PublishNext(_value); } else diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index b4b3399022..d989bb0706 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -1,9 +1,7 @@ using System; using Avalonia.Animation; using Avalonia.Data; -using Avalonia.Data.Core; using Avalonia.Metadata; -using Avalonia.Utilities; #nullable enable @@ -70,12 +68,5 @@ namespace Avalonia.Styling return Property.CreateSetterInstance(target, Value); } - - private struct SetterVisitorData - { - public IStyleable target; - public object? value; - public ISetterInstance? result; - } } } diff --git a/src/Avalonia.Base/Styling/StyleInstance.cs b/src/Avalonia.Base/Styling/StyleInstance.cs index 830cf49a0d..db96da6821 100644 --- a/src/Avalonia.Base/Styling/StyleInstance.cs +++ b/src/Avalonia.Base/Styling/StyleInstance.cs @@ -11,10 +11,10 @@ namespace Avalonia.Styling /// /// A which has been instanced on a control. /// - internal class StyleInstance : IStyleInstance, IStyleActivatorSink + internal sealed class StyleInstance : IStyleInstance, IStyleActivatorSink { - private readonly List? _setters; - private readonly List? _animations; + private readonly ISetterInstance[]? _setters; + private readonly IDisposable[]? _animations; private readonly IStyleActivator? _activator; private readonly Subject? _animationTrigger; @@ -30,41 +30,42 @@ namespace Avalonia.Styling _activator = activator; IsActive = _activator is null; - if (setters is object) + if (setters is not null) { var setterCount = setters.Count; - _setters = new List(setterCount); + _setters = new ISetterInstance[setterCount]; for (var i = 0; i < setterCount; ++i) { - _setters.Add(setters[i].Instance(Target)); + _setters[i] = setters[i].Instance(Target); } } - if (animations is object && target is Animatable animatable) + if (animations is not null && target is Animatable animatable) { var animationsCount = animations.Count; - _animations = new List(animationsCount); + _animations = new IDisposable[animationsCount]; _animationTrigger = new Subject(); for (var i = 0; i < animationsCount; ++i) { - _animations.Add(animations[i].Apply(animatable, null, _animationTrigger)); + _animations[i] = animations[i].Apply(animatable, null, _animationTrigger); } } } + public bool HasActivator => _activator is not null; public bool IsActive { get; private set; } public IStyle Source { get; } public IStyleable Target { get; } public void Start() { - var hasActivator = _activator is object; + var hasActivator = HasActivator; - if (_setters is object) + if (_setters is not null) { foreach (var setter in _setters) { @@ -76,7 +77,7 @@ namespace Avalonia.Styling { _activator!.Subscribe(this, 0); } - else if (_animationTrigger != null) + else if (_animationTrigger is not null) { _animationTrigger.OnNext(true); } @@ -84,7 +85,7 @@ namespace Avalonia.Styling public void Dispose() { - if (_setters is object) + if (_setters is not null) { foreach (var setter in _setters) { @@ -92,11 +93,11 @@ namespace Avalonia.Styling } } - if (_animations is object) + if (_animations is not null) { - foreach (var subscripion in _animations) + foreach (var subscription in _animations) { - subscripion.Dispose(); + subscription.Dispose(); } } @@ -111,7 +112,7 @@ namespace Avalonia.Styling _animationTrigger?.OnNext(value); - if (_setters is object) + if (_setters is not null) { if (IsActive) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index a1fd425571..e383c160e3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -60,7 +60,8 @@ namespace Avalonia.Diagnostics.ViewModels var styleDiagnostics = styledElement.GetStyleDiagnostics(); - foreach (var appliedStyle in styleDiagnostics.AppliedStyles) + // We need to place styles without activator first, such styles will be overwritten by ones with activators. + foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator)) { var styleSource = appliedStyle.Source; From 6c7dc426fcec124fcb2edb75be0ec1fa0bdb0afc Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 13 May 2022 23:59:09 +0200 Subject: [PATCH 09/25] Missing property changes. --- src/Avalonia.Base/DirectPropertyBase.cs | 5 ++--- src/Avalonia.Base/StyledPropertyBase.cs | 8 +++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 9c1ffce24c..efcb7dfecb 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -2,7 +2,6 @@ using Avalonia.Data; using Avalonia.Reactive; using Avalonia.Styling; -using Avalonia.Utilities; namespace Avalonia { @@ -188,10 +187,10 @@ namespace Avalonia } else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) { - return new PropertySetterLazyInstance( + return new PropertySetterTemplateInstance( target, this, - () => (TValue)template.Build()); + template); } else { diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index dd5eb703ea..da607720ff 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -1,9 +1,7 @@ using System; -using System.Diagnostics; using Avalonia.Data; using Avalonia.Reactive; using Avalonia.Styling; -using Avalonia.Utilities; namespace Avalonia { @@ -12,7 +10,7 @@ namespace Avalonia /// public abstract class StyledPropertyBase : AvaloniaProperty, IStyledPropertyAccessor { - private bool _inherits; + private readonly bool _inherits; /// /// Initializes a new instance of the class. @@ -243,10 +241,10 @@ namespace Avalonia } else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) { - return new PropertySetterLazyInstance( + return new PropertySetterTemplateInstance( target, this, - () => (TValue)template.Build()); + template); } else { From 90b67c3b9f73f2dfc6698f5f43486b0367c986cf Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 14 May 2022 01:22:34 +0200 Subject: [PATCH 10/25] Modify test setup so actual framework classes are used. --- tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs index ef2586fcb7..ed4c78aa3e 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs @@ -91,16 +91,13 @@ namespace Avalonia.Base.UnitTests.Styling [Fact] public void Setter_Should_Apply_Value_Without_Activator_With_Style_Priority() { - var control = new Mock(); - var style = Mock.Of