Browse Source

Merge branch 'master' into fixes/viewbox-logical-parent

Steven Kirk 3 years ago
parent
commit
dbfa0f361a
26 changed files with 435 additions and 139 deletions
  1. 6 0
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  2. 12 7
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  3. 2 3
      src/Avalonia.Base/DirectPropertyBase.cs
  4. 1 1
      src/Avalonia.Base/Input/InputElement.cs
  5. 3 5
      src/Avalonia.Base/StyledPropertyBase.cs
  6. 5 0
      src/Avalonia.Base/Styling/IStyleInstance.cs
  7. 6 4
      src/Avalonia.Base/Styling/PropertySetterInstance.cs
  8. 15 16
      src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs
  9. 0 9
      src/Avalonia.Base/Styling/Setter.cs
  10. 18 17
      src/Avalonia.Base/Styling/StyleInstance.cs
  11. 21 12
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  12. 43 32
      src/Avalonia.Controls/Canvas.cs
  13. 2 2
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  14. 11 11
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  15. 20 5
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  16. 1 1
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  17. 1 1
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  18. 17 2
      src/Avalonia.Controls/TextBox.cs
  19. 2 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  20. 1 0
      src/Avalonia.Themes.Default/Controls/TextBox.xaml
  21. 1 0
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  22. 2 2
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  23. 4 7
      tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
  24. 1 1
      tests/Avalonia.Controls.UnitTests/CalendarTests.cs
  25. 19 0
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  26. 221 0
      tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs

+ 6 - 0
samples/ControlCatalog/Pages/TextBoxPage.xaml

@@ -66,6 +66,12 @@
                  FontFamily="Comic Sans MS"
                  InputMethod.IsInputMethodEnabled="False"
                  Foreground="Red"/>
+        <TextBox AcceptsReturn="True" 
+                 TextWrapping="Wrap" 
+                 Width="200" 
+                 Height="125"
+                 LineHeight="32"
+                 Text="Multiline TextBox with TextWrapping and increased LineHeight.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
       </StackPanel>
       <StackPanel Orientation="Vertical" Spacing="8" Margin="8">
         <Label Classes="h2" Target="{Binding #firstResMFont}">res_m fonts</Label>

+ 12 - 7
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Data.Core.Plugins
             new Dictionary<(Type, string), PropertyInfo?>();
 
         /// <inheritdoc/>
-        public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
+        public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null;
 
         /// <summary>
         /// 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)
             {

+ 2 - 3
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<TValue>(
+                return new PropertySetterTemplateInstance<TValue>(
                     target,
                     this,
-                    () => (TValue)template.Build());
+                    template);
             }
             else
             {

+ 1 - 1
src/Avalonia.Base/Input/InputElement.cs

@@ -144,7 +144,7 @@ namespace Avalonia.Input
         /// </summary>
         public static readonly RoutedEvent<PointerEventArgs> PointerMovedEvent =
             RoutedEvent.Register<InputElement, PointerEventArgs>(
-                "PointerMove",
+                "PointerMoved",
                 RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
 
         /// <summary>

+ 3 - 5
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
     /// </summary>
     public abstract class StyledPropertyBase<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor
     {
-        private bool _inherits;
+        private readonly bool _inherits;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="StyledPropertyBase{T}"/> class.
@@ -243,10 +241,10 @@ namespace Avalonia
             }
             else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
             {
-                return new PropertySetterLazyInstance<TValue>(
+                return new PropertySetterTemplateInstance<TValue>(
                     target,
                     this,
-                    () => (TValue)template.Build());
+                    template);
             }
             else
             {

+ 5 - 0
src/Avalonia.Base/Styling/IStyleInstance.cs

@@ -14,6 +14,11 @@ namespace Avalonia.Styling
         /// </summary>
         IStyle Source { get; }
 
+        /// <summary>
+        /// Gets a value indicating whether this style has an activator.
+        /// </summary>
+        bool HasActivator { get; }
+        
         /// <summary>
         /// Gets a value indicating whether this style is active.
         /// </summary>

+ 6 - 4
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);
                 }
             }
         }

+ 15 - 16
src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs → src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs

@@ -11,42 +11,42 @@ namespace Avalonia.Styling
     /// evaluated.
     /// </summary>
     /// <typeparam name="T">The target property type.</typeparam>
-    internal class PropertySetterLazyInstance<T> : SingleSubscriberObservableBase<BindingValue<T>>,
+    internal class PropertySetterTemplateInstance<T> : SingleSubscriberObservableBase<BindingValue<T>>,
         ISetterInstance
     {
         private readonly IStyleable _target;
         private readonly StyledPropertyBase<T>? _styledProperty;
         private readonly DirectPropertyBase<T>? _directProperty;
-        private readonly Func<T> _valueFactory;
+        private readonly ITemplate _template;
         private BindingValue<T> _value;
         private IDisposable? _subscription;
         private bool _isActive;
 
-        public PropertySetterLazyInstance(
+        public PropertySetterTemplateInstance(
             IStyleable target,
             StyledPropertyBase<T> property,
-            Func<T> valueFactory)
+            ITemplate template)
         {
             _target = target;
             _styledProperty = property;
-            _valueFactory = valueFactory;
+            _template = template;
         }
 
-        public PropertySetterLazyInstance(
+        public PropertySetterTemplateInstance(
             IStyleable target,
             DirectPropertyBase<T> property,
-            Func<T> 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

+ 0 - 9
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;
-        }
     }
 }

+ 18 - 17
src/Avalonia.Base/Styling/StyleInstance.cs

@@ -11,10 +11,10 @@ namespace Avalonia.Styling
     /// <summary>
     /// A <see cref="Style"/> which has been instanced on a control.
     /// </summary>
-    internal class StyleInstance : IStyleInstance, IStyleActivatorSink
+    internal sealed class StyleInstance : IStyleInstance, IStyleActivatorSink
     {
-        private readonly List<ISetterInstance>? _setters;
-        private readonly List<IDisposable>? _animations;
+        private readonly ISetterInstance[]? _setters;
+        private readonly IDisposable[]? _animations;
         private readonly IStyleActivator? _activator;
         private readonly Subject<bool>? _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<ISetterInstance>(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<IDisposable>(animationsCount);
+                _animations = new IDisposable[animationsCount];
                 _animationTrigger = new Subject<bool>();
 
                 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)
                     {

+ 21 - 12
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -7,6 +7,7 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Globalization;
+using Avalonia.Collections.Pooled;
 using Avalonia.Controls.Metadata;
 using Avalonia.Data;
 using Avalonia.Input;
@@ -172,13 +173,13 @@ namespace Avalonia.Controls.Primitives
             if (MonthView != null)
             {
                 var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth;
-                var children = new List<IControl>(childCount);
+                using var children = new PooledList<IControl>(childCount);
 
                 for (int i = 0; i < Calendar.RowsPerMonth; i++)
                 {
                     if (_dayTitleTemplate != null)
                     {
-                        var cell = _dayTitleTemplate.Build();
+                        var cell = (Control) _dayTitleTemplate.Build();
                         cell.DataContext = string.Empty;
                         cell.SetValue(Grid.RowProperty, 0);
                         cell.SetValue(Grid.ColumnProperty, i);
@@ -186,11 +187,16 @@ namespace Avalonia.Controls.Primitives
                     }
                 }
 
+                EventHandler<PointerPressedEventArgs> cellMouseLeftButtonDown = Cell_MouseLeftButtonDown;
+                EventHandler<PointerReleasedEventArgs> cellMouseLeftButtonUp = Cell_MouseLeftButtonUp;
+                EventHandler<PointerEventArgs> cellMouseEnter = Cell_MouseEnter;
+                EventHandler<RoutedEventArgs> cellClick = Cell_Click;
+
                 for (int i = 1; i < Calendar.RowsPerMonth; i++)
                 {
                     for (int j = 0; j < Calendar.ColumnsPerMonth; j++)
                     {
-                        CalendarDayButton cell = new CalendarDayButton();
+                        var cell = new CalendarDayButton();
 
                         if (Owner != null)
                         {
@@ -198,10 +204,10 @@ namespace Avalonia.Controls.Primitives
                         }
                         cell.SetValue(Grid.RowProperty, i);
                         cell.SetValue(Grid.ColumnProperty, j);
-                        cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown;
-                        cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp;
-                        cell.PointerEnter += Cell_MouseEnter;
-                        cell.Click += Cell_Click;
+                        cell.CalendarDayButtonMouseDown += cellMouseLeftButtonDown;
+                        cell.CalendarDayButtonMouseUp += cellMouseLeftButtonUp;
+                        cell.PointerEnter += cellMouseEnter;
+                        cell.Click += cellClick;
                         children.Add(cell);
                     }
                 }
@@ -214,12 +220,15 @@ namespace Avalonia.Controls.Primitives
                 var childCount = Calendar.RowsPerYear * Calendar.ColumnsPerYear;
                 var children = new List<IControl>(childCount);
 
-                CalendarButton month;
+                EventHandler<PointerPressedEventArgs> monthCalendarButtonMouseDown = Month_CalendarButtonMouseDown;
+                EventHandler<PointerReleasedEventArgs> monthCalendarButtonMouseUp = Month_CalendarButtonMouseUp;
+                EventHandler<PointerEventArgs> monthMouseEnter = Month_MouseEnter;
+
                 for (int i = 0; i < Calendar.RowsPerYear; i++)
                 {
                     for (int j = 0; j < Calendar.ColumnsPerYear; j++)
                     {
-                        month = new CalendarButton();
+                        var month = new CalendarButton();
 
                         if (Owner != null)
                         {
@@ -227,9 +236,9 @@ namespace Avalonia.Controls.Primitives
                         }
                         month.SetValue(Grid.RowProperty, i);
                         month.SetValue(Grid.ColumnProperty, j);
-                        month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown;
-                        month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp;
-                        month.PointerEnter += Month_MouseEnter;
+                        month.CalendarLeftMouseButtonDown += monthCalendarButtonMouseDown;
+                        month.CalendarLeftMouseButtonUp += monthCalendarButtonMouseUp;
+                        month.PointerEnter += monthMouseEnter;
                         children.Add(month);
                     }
                 }

+ 43 - 32
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
         }
 
         /// <summary>
-        /// Arranges the control's children.
+        /// Arranges a single child.
         /// </summary>
-        /// <param name="finalSize">The size allocated to the control.</param>
-        /// <returns>The space taken.</returns>
-        protected override Size ArrangeOverride(Size finalSize)
+        /// <param name="child">The child to arrange.</param>
+        /// <param name="finalSize">The size allocated to the canvas.</param>
+        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));
+        }
+
+        /// <summary>
+        /// Arranges the control's children.
+        /// </summary>
+        /// <param name="finalSize">The size allocated to the control.</param>
+        /// <returns>The space taken.</returns>
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            foreach (Control child in Children)
+            {
+                ArrangeChild(child, finalSize);
             }
 
             return finalSize;

+ 2 - 2
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@@ -38,13 +38,13 @@ namespace Avalonia.Controls
         /// Defines the <see cref="Header"/> property
         /// </summary>
         public static readonly StyledProperty<object> HeaderProperty =
-            AvaloniaProperty.Register<DatePicker, object>(nameof(Header));
+            AvaloniaProperty.Register<TimePicker, object>(nameof(Header));
 
         /// <summary>
         /// Defines the <see cref="HeaderTemplate"/> property
         /// </summary>
         public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
-            AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate));
+            AvaloniaProperty.Register<TimePicker, IDataTemplate>(nameof(HeaderTemplate));
 
         /// <summary>
         /// Defines the <see cref="ClockIdentifier"/> property

+ 11 - 11
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -52,67 +52,67 @@ namespace Avalonia.Controls.Presenters
         /// <summary>
         /// Defines the <see cref="Foreground"/> property.
         /// </summary>
-        public static readonly AttachedProperty<IBrush?> ForegroundProperty =
+        public static readonly StyledProperty<IBrush?> ForegroundProperty =
             TextElement.ForegroundProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="FontFamily"/> property.
         /// </summary>
-        public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
+        public static readonly StyledProperty<FontFamily> FontFamilyProperty =
             TextElement.FontFamilyProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="FontSize"/> property.
         /// </summary>
-        public static readonly AttachedProperty<double> FontSizeProperty =
+        public static readonly StyledProperty<double> FontSizeProperty =
             TextElement.FontSizeProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="FontStyle"/> property.
         /// </summary>
-        public static readonly AttachedProperty<FontStyle> FontStyleProperty =
+        public static readonly StyledProperty<FontStyle> FontStyleProperty =
             TextElement.FontStyleProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="FontWeight"/> property.
         /// </summary>
-        public static readonly AttachedProperty<FontWeight> FontWeightProperty =
+        public static readonly StyledProperty<FontWeight> FontWeightProperty =
             TextElement.FontWeightProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="FontStretch"/> property.
         /// </summary>
-        public static readonly AttachedProperty<FontStretch> FontStretchProperty =
+        public static readonly StyledProperty<FontStretch> FontStretchProperty =
             TextElement.FontStretchProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="TextAlignment"/> property
         /// </summary>
-        public static readonly AttachedProperty<TextAlignment> TextAlignmentProperty =
+        public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
             TextBlock.TextAlignmentProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="TextWrapping"/> property
         /// </summary>
-        public static readonly AttachedProperty<TextWrapping> TextWrappingProperty =
+        public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
             TextBlock.TextWrappingProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="TextTrimming"/> property
         /// </summary>
-        public static readonly AttachedProperty<TextTrimming> TextTrimmingProperty =
+        public static readonly StyledProperty<TextTrimming> TextTrimmingProperty =
             TextBlock.TextTrimmingProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="LineHeight"/> property
         /// </summary>
-        public static readonly AttachedProperty<double> LineHeightProperty =
+        public static readonly StyledProperty<double> LineHeightProperty =
             TextBlock.LineHeightProperty.AddOwner<ContentPresenter>();
 
         /// <summary>
         /// Defines the <see cref="MaxLines"/> property
         /// </summary>
-        public static readonly AttachedProperty<int> MaxLinesProperty =
+        public static readonly StyledProperty<int> MaxLinesProperty =
             TextBlock.MaxLinesProperty.AddOwner<ContentPresenter>();
                 
         /// <summary>

+ 20 - 5
src/Avalonia.Controls/Presenters/TextPresenter.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Controls.Presenters
 
         public static readonly StyledProperty<IBrush?> CaretBrushProperty =
             AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(CaretBrush));
-
+        
         public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty =
             TextBox.SelectionStartProperty.AddOwner<TextPresenter>(
                 o => o.SelectionStart,
@@ -43,7 +43,7 @@ namespace Avalonia.Controls.Presenters
             TextBox.SelectionEndProperty.AddOwner<TextPresenter>(
                 o => o.SelectionEnd,
                 (o, v) => o.SelectionEnd = v);
-
+        
         /// <summary>
         /// Defines the <see cref="Text"/> property.
         /// </summary>
@@ -65,6 +65,12 @@ namespace Avalonia.Controls.Presenters
         public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
             TextBlock.TextWrappingProperty.AddOwner<TextPresenter>();
 
+        /// <summary>
+        /// Defines the <see cref="LineHeight"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> LineHeightProperty =
+            TextBlock.LineHeightProperty.AddOwner<TextPresenter>();
+        
         /// <summary>
         /// Defines the <see cref="Background"/> property.
         /// </summary>
@@ -179,6 +185,15 @@ namespace Avalonia.Controls.Presenters
             get => GetValue(TextWrappingProperty);
             set => SetValue(TextWrappingProperty, value);
         }
+        
+        /// <summary>
+        /// Gets or sets the line height. By default, this is set to <see cref="double.NaN"/>, which determines the appropriate height automatically.
+        /// </summary>
+        public double LineHeight
+        {
+            get => GetValue(LineHeightProperty);
+            set => SetValue(LineHeightProperty, value);
+        }
 
         /// <summary>
         /// Gets or sets the text alignment.
@@ -253,7 +268,7 @@ namespace Avalonia.Controls.Presenters
             get => GetValue(CaretBrushProperty);
             set => SetValue(CaretBrushProperty, value);
         }
-
+        
         public int SelectionStart
         {
             get
@@ -281,7 +296,7 @@ namespace Avalonia.Controls.Presenters
                 SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
             }
         }
-
+        
         protected override bool BypassFlowDirectionPolicies => true;
 
         /// <summary>
@@ -301,7 +316,7 @@ namespace Avalonia.Controls.Presenters
             
             var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment,
                 TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, 
-                flowDirection: FlowDirection);
+                flowDirection: FlowDirection, lineHeight: LineHeight);
 
             return textLayout;
         }

+ 1 - 1
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@@ -105,7 +105,7 @@ namespace Avalonia.Controls.Primitives
                     }
                     else
                     {
-                        child.Arrange(new Rect(finalSize));
+                        ArrangeChild((Control) child, finalSize);
                     }
                 }
             }

+ 1 - 1
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@@ -118,7 +118,7 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="WrapSelection"/> property.
         /// </summary>
         public static readonly StyledProperty<bool> WrapSelectionProperty =
-            AvaloniaProperty.Register<ItemsControl, bool>(nameof(WrapSelection), defaultValue: false);
+            AvaloniaProperty.Register<SelectingItemsControl, bool>(nameof(WrapSelection), defaultValue: false);
 
         private static readonly IList Empty = Array.Empty<object>();
         private string _textSearchTerm = string.Empty;

+ 17 - 2
src/Avalonia.Controls/TextBox.cs

@@ -79,8 +79,8 @@ namespace Avalonia.Controls
             AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
 
         public static readonly StyledProperty<int> MaxLinesProperty =
-      AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
-
+            AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
+        
         public static readonly DirectProperty<TextBox, string?> TextProperty =
             TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
                 o => o.Text,
@@ -105,6 +105,12 @@ namespace Avalonia.Controls
 
         public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
             TextBlock.TextWrappingProperty.AddOwner<TextBox>();
+        
+        /// <summary>
+        /// Defines see <see cref="TextPresenter.LineHeight"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> LineHeightProperty =
+            TextBlock.LineHeightProperty.AddOwner<TextBox>();
 
         public static readonly StyledProperty<string?> WatermarkProperty =
             AvaloniaProperty.Register<TextBox, string?>(nameof(Watermark));
@@ -358,6 +364,15 @@ namespace Avalonia.Controls
             get { return GetValue(MaxLinesProperty); }
             set { SetValue(MaxLinesProperty, value); }
         }
+        
+        /// <summary>
+        /// Gets or sets the line height.
+        /// </summary>
+        public double LineHeight
+        {
+            get { return GetValue(LineHeightProperty); }
+            set { SetValue(LineHeightProperty, value); }
+        }
 
         [Content]
         public string? Text

+ 2 - 1
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;
 

+ 1 - 0
src/Avalonia.Themes.Default/Controls/TextBox.xaml

@@ -76,6 +76,7 @@
                                     SelectionEnd="{TemplateBinding SelectionEnd}"
                                     TextAlignment="{TemplateBinding TextAlignment}"
                                     TextWrapping="{TemplateBinding TextWrapping}"
+                                    LineHeight="{TemplateBinding LineHeight}"
                                     PasswordChar="{TemplateBinding PasswordChar}"
                                     RevealPassword="{TemplateBinding RevealPassword}"
                                     SelectionBrush="{TemplateBinding SelectionBrush}"

+ 1 - 0
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@@ -89,6 +89,7 @@
                                       SelectionEnd="{TemplateBinding SelectionEnd}"
                                       TextAlignment="{TemplateBinding TextAlignment}"
                                       TextWrapping="{TemplateBinding TextWrapping}"
+                                      LineHeight="{TemplateBinding LineHeight}"
                                       PasswordChar="{TemplateBinding PasswordChar}"
                                       RevealPassword="{TemplateBinding RevealPassword}"
                                       SelectionBrush="{TemplateBinding SelectionBrush}"

+ 2 - 2
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@@ -247,10 +247,10 @@ namespace Avalonia.Base.UnitTests.Input
                 new[]
                 {
                         ((object?)decorator, "PointerEnter"),
-                        (decorator, "PointerMove"),
+                        (decorator, "PointerMoved"),
                         (decorator, "PointerLeave"),
                         (canvas, "PointerEnter"),
-                        (canvas, "PointerMove")
+                        (canvas, "PointerMoved")
                 },
                 result);
         }

+ 4 - 7
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<IStyleable>();
-            var style = Mock.Of<Style>();
+            var control = new Control();
             var setter = new Setter(TextBlock.TagProperty, "foo");
 
-            setter.Instance(control.Object).Start(false);
+            setter.Instance(control).Start(false);
 
-            control.Verify(x => x.SetValue(
-                TextBlock.TagProperty,
-                "foo",
-                BindingPriority.Style));
+            Assert.Equal("foo", control.Tag);
+            Assert.Equal(BindingPriority.Style, control.GetDiagnostic(TextBlock.TagProperty).Priority);
         }
 
         [Fact]

+ 1 - 1
tests/Avalonia.Controls.UnitTests/CalendarTests.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Controls.UnitTests
                 first.Day == second.Day;
         }
 
-        [Fact(Skip ="FIX ME ASAP")]
+        [Fact]
         public void SelectedDatesChanged_Should_Fire_When_SelectedDate_Set()
         {
             bool handled = false;

+ 19 - 0
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
         {

+ 221 - 0
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<KeyValuePair<string, object>>
+{
+    private Dictionary<string, object> _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<KeyValuePair<string, object>> 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<PropertyInfo>();
+            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<object>(), It.IsAny<object[]>()))
+                .Returns((object target, object [] _) => ((DynamicReflectableType)target)._dic.GetValueOrDefault(name));
+            propInfo.Setup(x => x.SetValue(It.IsAny<object>(), It.IsAny<object>(), It.IsAny<object[]>()))
+                .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
+
+    }
+}