瀏覽代碼

Merge branch 'master' into win32-sync-context-fix

Dariusz Komosiński 5 年之前
父節點
當前提交
9ab167f6f2

+ 5 - 0
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@@ -155,6 +155,11 @@ namespace Avalonia.Controls.Presenters
             }
         }
 
+        protected override void PanelCreated(IPanel panel)
+        {
+            ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+        }
+
         /// <summary>
         /// Moves to the selected page, animating if a <see cref="PageTransition"/> is set.
         /// </summary>

+ 0 - 2
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@@ -229,8 +229,6 @@ namespace Avalonia.Controls.Presenters
             }
 
             PanelCreated(Panel);
-
-            ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
         }
 
         /// <summary>

+ 1 - 1
src/Avalonia.Controls/RelativePanel.AttachedProperties.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Controls
             AlignVerticalCenterWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
             BelowProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
             LeftOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
-            LeftOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
+            RightOfProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
         }
 
         /// <summary>

+ 335 - 150
src/Avalonia.Controls/RelativePanel.cs

@@ -1,4 +1,4 @@
-/// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs
+// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -14,25 +14,28 @@ namespace Avalonia.Controls
 
         public RelativePanel() => _childGraph = new Graph();
 
-        protected override Size MeasureOverride(Size availableSize)
+        private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child)
         {
-            foreach (var child in Children)
+            var dependency = child.GetValue(property);
+
+            if (dependency is Layoutable layoutable)
             {
-                child?.Measure(availableSize);
+                if (Children.Contains((ILayoutable)layoutable))
+                    return layoutable;
+
+                throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}");
             }
 
-            return availableSize;
+            return null;
         }
 
-        protected override Size ArrangeOverride(Size arrangeSize)
+        protected override Size MeasureOverride(Size availableSize)
         {
-            _childGraph.Reset(arrangeSize);
-
-            foreach (var child in Children.OfType<Layoutable>())
+            _childGraph.Clear();
+            foreach (Layoutable child in Children)
             {
                 if (child == null)
                     continue;
-
                 var node = _childGraph.AddNode(child);
 
                 node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child));
@@ -47,63 +50,44 @@ namespace Avalonia.Controls
 
                 node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child));
                 node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child));
-            }
 
-            if (_childGraph.CheckCyclic())
-            {
-                throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete.");
             }
+            _childGraph.Measure(availableSize);
 
-            var size = new Size();
-
-            foreach (var child in Children)
-            {
-                if (child.Bounds.Bottom > size.Height)
-                {
-                    size = size.WithHeight(child.Bounds.Bottom);
-                }
+            _childGraph.Reset();
+            var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN());
+            _childGraph.Reset();
+            _childGraph.Measure(boundingSize);
+            return boundingSize;
+        }
 
-                if (child.Bounds.Right > size.Width)
-                {
-                    size = size.WithWidth(child.Bounds.Right);
-                }
-            }
+        protected override Size ArrangeOverride(Size arrangeSize)
+        {
+            _childGraph.GetNodes().Do(node => node.Arrange(arrangeSize));
+            return arrangeSize;
+        }
 
-            if (VerticalAlignment == VerticalAlignment.Stretch)
-            {
-                size = size.WithHeight(arrangeSize.Height);
-            }
+        private class GraphNode
+        {
+            public bool Measured { get; set; }
 
-            if (HorizontalAlignment == HorizontalAlignment.Stretch)
-            {
-                size = size.WithWidth(arrangeSize.Width);
-            }
+            public Layoutable Element { get; }
 
-            return size;
-        }
+            private bool HorizontalOffsetFlag { get; set; }
 
-        private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child)
-        {
-            var dependency = child.GetValue(property);
+            private bool VerticalOffsetFlag { get; set; }
 
-            if (dependency is Layoutable layoutable)
-            {
-                if (Children.Contains((ILayoutable)layoutable))
-                    return layoutable;
+            private Size BoundingSize { get; set; }
 
-                throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}");
-            }
+            public Size OriginDesiredSize { get; set; }
 
-            return null;
-        }
+            public double Left { get; set; } = double.NaN;
 
-        private class GraphNode
-        {
-            public Point Position { get; set; }
+            public double Top { get; set; } = double.NaN;
 
-            public bool Arranged { get; set; }
+            public double Right { get; set; } = double.NaN;
 
-            public Layoutable Element { get; }
+            public double Bottom { get; set; } = double.NaN;
 
             public HashSet<GraphNode> OutgoingNodes { get; }
 
@@ -132,19 +116,101 @@ namespace Avalonia.Controls
                 OutgoingNodes = new HashSet<GraphNode>();
                 Element = element;
             }
+
+            public void Arrange(Size arrangeSize) => Element.Arrange(new Rect(Left, Top, Math.Max(arrangeSize.Width - Left - Right, 0), Math.Max(arrangeSize.Height - Top - Bottom, 0)));
+
+            public void Reset()
+            {
+                Left = double.NaN;
+                Top = double.NaN;
+                Right = double.NaN;
+                Bottom = double.NaN;
+                Measured = false;
+            }
+
+            public Size GetBoundingSize()
+            {
+                if (Measured)
+                    return BoundingSize;
+
+                if (!OutgoingNodes.Any())
+                {
+                    BoundingSize = Element.DesiredSize;
+                    Measured = true;
+                }
+                else
+                {
+                    BoundingSize = GetBoundingSize(this, Element.DesiredSize, OutgoingNodes);
+                    Measured = true;
+                }
+
+                return BoundingSize;
+            }
+
+            private static Size GetBoundingSize(GraphNode prevNode, Size prevSize, IEnumerable<GraphNode> nodes)
+            {
+                foreach (var node in nodes)
+                {
+                    if (node.Measured || !node.OutgoingNodes.Any())
+                    {
+                        if (prevNode.LeftOfNode != null && prevNode.LeftOfNode == node ||
+                            prevNode.RightOfNode != null && prevNode.RightOfNode == node)
+                        {
+                            prevSize = prevSize.WithWidth(prevSize.Width + node.BoundingSize.Width);
+                            if (GetAlignHorizontalCenterWithPanel(node.Element) || node.HorizontalOffsetFlag)
+                            {
+                                prevSize = prevSize.WithWidth(prevSize.Width + prevNode.OriginDesiredSize.Width);
+                                prevNode.HorizontalOffsetFlag = true;
+                            }
+                            if (node.VerticalOffsetFlag)
+                            {
+                                prevNode.VerticalOffsetFlag = true;
+                            }
+                        }
+
+                        if (prevNode.AboveNode != null && prevNode.AboveNode == node ||
+                            prevNode.BelowNode != null && prevNode.BelowNode == node)
+                        {
+                            prevSize = prevSize.WithHeight(prevSize.Height + node.BoundingSize.Height);
+                            if (GetAlignVerticalCenterWithPanel(node.Element) || node.VerticalOffsetFlag)
+                            {
+                                prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height);
+                                prevNode.VerticalOffsetFlag = true;
+                            }
+                            if (node.HorizontalOffsetFlag)
+                            {
+                                prevNode.HorizontalOffsetFlag = true;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        return GetBoundingSize(node, prevSize, node.OutgoingNodes);
+                    }
+                }
+
+                return prevSize;
+            }
         }
 
         private class Graph
         {
             private readonly Dictionary<AvaloniaObject, GraphNode> _nodeDic;
 
-            private Size _arrangeSize;
+            private Size AvailableSize { get; set; }
 
-            public Graph()
+            public Graph() => _nodeDic = new Dictionary<AvaloniaObject, GraphNode>();
+
+            public IEnumerable<GraphNode> GetNodes() => _nodeDic.Values;
+
+            public void Clear()
             {
-                _nodeDic = new Dictionary<AvaloniaObject, GraphNode>();
+                AvailableSize = new Size();
+                _nodeDic.Clear();
             }
 
+            public void Reset() => _nodeDic.Values.Do(node => node.Reset());
+
             public GraphNode? AddLink(GraphNode from, Layoutable? to)
             {
                 if (to == null)
@@ -177,177 +243,296 @@ namespace Avalonia.Controls
                 return _nodeDic[value];
             }
 
-            public void Reset(Size arrangeSize)
+            public void Measure(Size availableSize)
             {
-                _arrangeSize = arrangeSize;
-                _nodeDic.Clear();
+                AvailableSize = availableSize;
+                Measure(_nodeDic.Values, null);
             }
 
-            public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null);
-
-            private bool CheckCyclic(IEnumerable<GraphNode> nodes, HashSet<Layoutable>? set)
+            private void Measure(IEnumerable<GraphNode> nodes, HashSet<AvaloniaObject>? set)
             {
-                set ??= new HashSet<Layoutable>();
+                set ??= new HashSet<AvaloniaObject>();
 
                 foreach (var node in nodes)
                 {
-                    if (!node.Arranged && node.OutgoingNodes.Count == 0)
+                    /*
+                     * 该节点无任何依赖,所以从这里开始计算元素位置。
+                     * 因为无任何依赖,所以忽略同级元素
+                     */
+                    if (!node.Measured && !node.OutgoingNodes.Any())
                     {
-                        ArrangeChild(node, true);
+                        MeasureChild(node);
                         continue;
                     }
 
-                    if (node.OutgoingNodes.All(item => item.Arranged))
+                    //  判断依赖元素是否全部排列完毕
+                    if (node.OutgoingNodes.All(item => item.Measured))
                     {
-                        ArrangeChild(node);
+                        MeasureChild(node);
                         continue;
                     }
 
+                    //  判断是否有循环
                     if (!set.Add(node.Element))
-                        return true;
+                        throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete.");
 
-                    return CheckCyclic(node.OutgoingNodes, set);
-                }
+                    //  没有循环,且有依赖,则继续往下
+                    Measure(node.OutgoingNodes, set);
 
-                return false;
+                    if (!node.Measured)
+                    {
+                        MeasureChild(node);
+                    }
+                }
             }
 
-            private void ArrangeChild(GraphNode node, bool ignoneSibling = false)
+            private void MeasureChild(GraphNode node)
             {
                 var child = node.Element;
-                var childSize = child.DesiredSize;
-                var childPos = new Point();
+                child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+                node.OriginDesiredSize = child.DesiredSize;
 
-                if (GetAlignHorizontalCenterWithPanel(child))
+                var alignLeftWithPanel = GetAlignLeftWithPanel(child);
+                var alignTopWithPanel = GetAlignTopWithPanel(child);
+                var alignRightWithPanel = GetAlignRightWithPanel(child);
+                var alignBottomWithPanel = GetAlignBottomWithPanel(child);
+
+                if (alignLeftWithPanel)
+                    node.Left = 0;
+                if (alignTopWithPanel)
+                    node.Top = 0;
+                if (alignRightWithPanel)
+                    node.Right = 0;
+                if (alignBottomWithPanel)
+                    node.Bottom = 0;
+
+                if (node.AlignLeftWithNode != null)
                 {
-                    childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2);
+                    node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5;
                 }
 
-                if (GetAlignVerticalCenterWithPanel(child))
+                if (node.AlignTopWithNode != null)
                 {
-                    childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2);
+                    node.Top = node.Top.IsNaN() ? node.AlignTopWithNode.Top : node.AlignTopWithNode.Top * 0.5;
                 }
 
-                var alignLeftWithPanel = GetAlignLeftWithPanel(child);
-                var alignTopWithPanel = GetAlignTopWithPanel(child);
-                var alignRightWithPanel = GetAlignRightWithPanel(child);
-                var alignBottomWithPanel = GetAlignBottomWithPanel(child);
+                if (node.AlignRightWithNode != null)
+                {
+                    node.Right = node.Right.IsNaN()
+                        ? node.AlignRightWithNode.Right
+                        : node.AlignRightWithNode.Right * 0.5;
+                }
 
-                if (!ignoneSibling)
+                if (node.AlignBottomWithNode != null)
                 {
-                    if (node.LeftOfNode != null)
-                    {
-                        childPos = childPos.WithX(node.LeftOfNode.Position.X - childSize.Width);
-                    }
+                    node.Bottom = node.Bottom.IsNaN()
+                        ? node.AlignBottomWithNode.Bottom
+                        : node.AlignBottomWithNode.Bottom * 0.5;
+                }
 
-                    if (node.AboveNode != null)
-                    {
-                        childPos = childPos.WithY(node.AboveNode.Position.Y - childSize.Height);
-                    }
+                var availableHeight = AvailableSize.Height - node.Top - node.Bottom;
+                if (availableHeight.IsNaN())
+                {
+                    availableHeight = AvailableSize.Height;
 
-                    if (node.RightOfNode != null)
+                    if (!node.Top.IsNaN() && node.Bottom.IsNaN())
                     {
-                        childPos = childPos.WithX(node.RightOfNode.Position.X + node.RightOfNode.Element.DesiredSize.Width);
+                        availableHeight -= node.Top;
                     }
-
-                    if (node.BelowNode != null)
+                    else if (node.Top.IsNaN() && !node.Bottom.IsNaN())
                     {
-                        childPos = childPos.WithY(node.BelowNode.Position.Y + node.BelowNode.Element.DesiredSize.Height);
+                        availableHeight -= node.Bottom;
                     }
+                }
+
+                var availableWidth = AvailableSize.Width - node.Left - node.Right;
+                if (availableWidth.IsNaN())
+                {
+                    availableWidth = AvailableSize.Width;
 
-                    if (node.AlignHorizontalCenterWith != null)
+                    if (!node.Left.IsNaN() && node.Right.IsNaN())
                     {
-                        childPos = childPos.WithX(node.AlignHorizontalCenterWith.Position.X +
-                                     (node.AlignHorizontalCenterWith.Element.DesiredSize.Width - childSize.Width) / 2);
+                        availableWidth -= node.Left;
                     }
-
-                    if (node.AlignVerticalCenterWith != null)
+                    else if (node.Left.IsNaN() && !node.Right.IsNaN())
                     {
-                        childPos = childPos.WithY(node.AlignVerticalCenterWith.Position.Y +
-                                     (node.AlignVerticalCenterWith.Element.DesiredSize.Height - childSize.Height) / 2);
+                        availableWidth -= node.Right;
                     }
+                }
+
+                child.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0)));
+                var childSize = child.DesiredSize;
+
+                if (node.LeftOfNode != null && node.Left.IsNaN())
+                {
+                    node.Left = node.LeftOfNode.Left - childSize.Width;
+                }
+
+                if (node.AboveNode != null && node.Top.IsNaN())
+                {
+                    node.Top = node.AboveNode.Top - childSize.Height;
+                }
 
-                    if (node.AlignLeftWithNode != null)
+                if (node.RightOfNode != null)
+                {
+                    if (node.Right.IsNaN())
                     {
-                        childPos = childPos.WithX(node.AlignLeftWithNode.Position.X);
+                        node.Right = node.RightOfNode.Right - childSize.Width;
                     }
 
-                    if (node.AlignTopWithNode != null)
+                    if (node.Left.IsNaN())
                     {
-                        childPos = childPos.WithY(node.AlignTopWithNode.Position.Y);
+                        node.Left = AvailableSize.Width - node.RightOfNode.Right;
                     }
+                }
 
-                    if (node.AlignRightWithNode != null)
+                if (node.BelowNode != null)
+                {
+                    if (node.Bottom.IsNaN())
                     {
-                        childPos = childPos.WithX(node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width);
+                        node.Bottom = node.BelowNode.Bottom - childSize.Height;
                     }
 
-                    if (node.AlignBottomWithNode != null)
+                    if (node.Top.IsNaN())
                     {
-                        childPos = childPos.WithY(node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height);
+                        node.Top = AvailableSize.Height - node.BelowNode.Bottom;
                     }
                 }
 
-                if (alignLeftWithPanel)
+                if (node.AlignHorizontalCenterWith != null)
                 {
-                    if (node.AlignRightWithNode != null)
-                    {
-                        childPos = childPos.WithX((node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width) / 2);
-                    }
+                    var halfWidthLeft = (AvailableSize.Width + node.AlignHorizontalCenterWith.Left - node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5;
+                    var halfWidthRight = (AvailableSize.Width - node.AlignHorizontalCenterWith.Left + node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5;
+
+                    if (node.Left.IsNaN())
+                        node.Left = halfWidthLeft;
                     else
-                    {
-                        childPos = childPos.WithX(0);
-                    }
+                        node.Left = (node.Left + halfWidthLeft) * 0.5;
+
+                    if (node.Right.IsNaN())
+                        node.Right = halfWidthRight;
+                    else
+                        node.Right = (node.Right + halfWidthRight) * 0.5;
                 }
 
-                if (alignTopWithPanel)
+                if (node.AlignVerticalCenterWith != null)
                 {
-                    if (node.AlignBottomWithNode != null)
-                    {
-                        childPos = childPos.WithY((node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height) / 2);
-                    }
+                    var halfHeightTop = (AvailableSize.Height + node.AlignVerticalCenterWith.Top - node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5;
+                    var halfHeightBottom = (AvailableSize.Height - node.AlignVerticalCenterWith.Top + node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5;
+
+                    if (node.Top.IsNaN())
+                        node.Top = halfHeightTop;
                     else
-                    {
-                        childPos = childPos.WithY(0);
-                    }
+                        node.Top = (node.Top + halfHeightTop) * 0.5;
+
+                    if (node.Bottom.IsNaN())
+                        node.Bottom = halfHeightBottom;
+                    else
+                        node.Bottom = (node.Bottom + halfHeightBottom) * 0.5;
                 }
 
-                if (alignRightWithPanel)
+                if (GetAlignHorizontalCenterWithPanel(child))
                 {
-                    if (alignLeftWithPanel)
-                    {
-                        childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2);
-                    }
-                    else if (node.AlignLeftWithNode == null)
-                    {
-                        childPos = childPos.WithX(_arrangeSize.Width - childSize.Width);
-                    }
+                    var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5;
+
+                    if (node.Left.IsNaN())
+                        node.Left = halfSubWidth;
                     else
-                    {
-                        childPos = childPos.WithX((_arrangeSize.Width + node.AlignLeftWithNode.Position.X - childSize.Width) / 2);
-                    }
+                        node.Left = (node.Left + halfSubWidth) * 0.5;
+
+                    if (node.Right.IsNaN())
+                        node.Right = halfSubWidth;
+                    else
+                        node.Right = (node.Right + halfSubWidth) * 0.5;
                 }
 
-                if (alignBottomWithPanel)
+                if (GetAlignVerticalCenterWithPanel(child))
                 {
-                    if (alignTopWithPanel)
-                    {
-                        childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2);
-                    }
-                    else if (node.AlignTopWithNode == null)
+                    var halfSubHeight = (AvailableSize.Height - childSize.Height) * 0.5;
+
+                    if (node.Top.IsNaN())
+                        node.Top = halfSubHeight;
+                    else
+                        node.Top = (node.Top + halfSubHeight) * 0.5;
+
+                    if (node.Bottom.IsNaN())
+                        node.Bottom = halfSubHeight;
+                    else
+                        node.Bottom = (node.Bottom + halfSubHeight) * 0.5;
+                }
+
+                if (node.Left.IsNaN())
+                {
+                    if (!node.Right.IsNaN())
+                        node.Left = AvailableSize.Width - node.Right - childSize.Width;
+                    else
                     {
-                        childPos = childPos.WithY(_arrangeSize.Height - childSize.Height);
+                        node.Left = 0;
+                        node.Right = AvailableSize.Width - childSize.Width;
                     }
+                }
+                else if (!node.Left.IsNaN() && node.Right.IsNaN())
+                {
+                    node.Right = AvailableSize.Width - node.Left - childSize.Width;
+                }
+
+                if (node.Top.IsNaN())
+                {
+                    if (!node.Bottom.IsNaN())
+                        node.Top = AvailableSize.Height - node.Bottom - childSize.Height;
                     else
                     {
-                        childPos = childPos.WithY((_arrangeSize.Height + node.AlignTopWithNode.Position.Y - childSize.Height) / 2);
+                        node.Top = 0;
+                        node.Bottom = AvailableSize.Height - childSize.Height;
                     }
                 }
+                else if (!node.Top.IsNaN() && node.Bottom.IsNaN())
+                {
+                    node.Bottom = AvailableSize.Height - node.Top - childSize.Height;
+                }
+
+                node.Measured = true;
+            }
+
+            public Size GetBoundingSize(bool calcWidth, bool calcHeight)
+            {
+                var boundingSize = new Size();
+
+                foreach (var node in _nodeDic.Values)
+                {
+                    var size = node.GetBoundingSize();
+                    boundingSize = boundingSize.WithWidth(Math.Max(boundingSize.Width, size.Width));
+                    boundingSize = boundingSize.WithHeight(Math.Max(boundingSize.Height, size.Height));
+                }
 
-                child.Arrange(new Rect(childPos.X, childPos.Y, childSize.Width, childSize.Height));
-                node.Position = childPos;
-                node.Arranged = true;
+                boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : AvailableSize.Width);
+                boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : AvailableSize.Height);
+                return boundingSize;
             }
         }
     }
+
+    internal static partial class Extensions
+    {
+        /// <summary>
+        ///     Returns a value that indicates whether the specified value is not a number ().
+        /// </summary>
+        /// <param name="d">A double-precision floating-point number.</param>
+        /// <returns>true if  evaluates to ; otherwise, false.</returns>
+        public static bool IsNaN(this double d)
+        {
+            return double.IsNaN(d);
+        }
+
+        public static IEnumerable<TSource> Do<TSource>(this IEnumerable<TSource> source, Action<TSource> predicate)
+        {
+            var enumerable = source as IList<TSource> ?? source.ToList();
+            foreach (var item in enumerable)
+            {
+                predicate.Invoke(item);
+            }
+
+            return enumerable;
+        }
+    }
 }

+ 1 - 1
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server
                             GetAvaloniaInputModifiers(pressed.Modifiers)));
                     }, DispatcherPriority.Input);
                 }
-                if (obj is PointerPressedEventMessage released)
+                if (obj is PointerReleasedEventMessage released)
                 {
                     Dispatcher.UIThread.Post(() =>
                     {

+ 2 - 1
src/Avalonia.Themes.Default/ToggleSwitch.xaml

@@ -43,7 +43,8 @@
     <Setter Property="Foreground" Value="{DynamicResource ToggleSwitchContentForeground}" />
     <Setter Property="HorizontalAlignment" Value="Left" />
     <Setter Property="VerticalAlignment" Value="Center" />
-    <Setter Property="HorizontalContentAlignment" Value="Left" />    
+    <Setter Property="HorizontalContentAlignment" Value="Left" />
+    <Setter Property="VerticalContentAlignment" Value="Center" />
     <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
     <Setter Property="Template">
       <ControlTemplate>

+ 2 - 1
src/Avalonia.Themes.Fluent/ToggleSwitch.xaml

@@ -43,7 +43,8 @@
     <Setter Property="Foreground" Value="{DynamicResource ToggleSwitchContentForeground}" />
     <Setter Property="HorizontalAlignment" Value="Left" />
     <Setter Property="VerticalAlignment" Value="Center" />
-    <Setter Property="HorizontalContentAlignment" Value="Left" />    
+    <Setter Property="HorizontalContentAlignment" Value="Left" />
+    <Setter Property="VerticalContentAlignment" Value="Center" />
     <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
     <Setter Property="Template">
       <ControlTemplate>

+ 2 - 2
src/Markup/Avalonia.Markup/Data/BindingBase.cs

@@ -137,9 +137,9 @@ namespace Avalonia.Data
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
-            if (!(target is IStyledElement))
+            if (!(target is IDataContextProvider))
             {
-                target = anchor as IStyledElement;
+                target = anchor as IDataContextProvider;
 
                 if (target == null)
                 {

+ 16 - 2
tests/Avalonia.Controls.UnitTests/ApplicationTests.cs

@@ -1,6 +1,5 @@
 using System;
-using System.Collections.Generic;
-using Avalonia.Threading;
+using Avalonia.Data;
 using Avalonia.UnitTests;
 using Xunit;
 
@@ -32,5 +31,20 @@ namespace Avalonia.Controls.UnitTests
                 Assert.True(raised);
             }
         }
+
+        [Fact]
+        public void Can_Bind_To_DataContext()
+        {
+            using (UnitTestApplication.Start())
+            {
+                var application = Application.Current;
+
+                application.DataContext = "Test";
+
+                application.Bind(Application.NameProperty, new Binding("."));
+
+                Assert.Equal("Test", Application.Current.Name);
+            }
+        }
     }
 }

+ 19 - 0
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@@ -60,6 +60,25 @@ namespace Avalonia.Controls.UnitTests.Presenters
             Assert.IsType<ListBoxItem>(target.Panel.Children[1]);
         }
 
+        [Fact]
+        public void Should_Create_Containers_Only_Once()
+        {
+            var parent = new TestItemsControl();
+            var target = new ItemsPresenter
+            {
+                Items = new[] { "foo", "bar" },
+                [StyledElement.TemplatedParentProperty] = parent,
+            };
+            var raised = 0;
+
+            parent.ItemContainerGenerator.Materialized += (s, e) => ++raised;
+
+            target.ApplyTemplate();
+
+            Assert.Equal(2, target.Panel.Children.Count);
+            Assert.Equal(2, raised);
+        }
+
         [Fact]
         public void ItemContainerGenerator_Should_Be_Picked_Up_From_TemplatedControl()
         {

+ 27 - 0
tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs

@@ -31,6 +31,7 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new Rect(20, 0, 20, 20), target.Children[1].Bounds);
         }
 
+        [Fact]
         public void Lays_Out_1_Child_Below_the_other()
         {
             var rect1 = new Rectangle { Height = 20, Width = 20 };
@@ -55,5 +56,31 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds);
             Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds);
         }
+
+        [Fact]
+        public void RelativePanel_Can_Center()
+        {
+            var rect1 = new Rectangle { Height = 20, Width = 20 };
+            var rect2 = new Rectangle { Height = 20, Width = 20 };
+
+            var target = new RelativePanel
+            {
+                VerticalAlignment = Layout.VerticalAlignment.Center,
+                HorizontalAlignment = Layout.HorizontalAlignment.Center,
+                Children =
+                {
+                    rect1, rect2
+                }
+            };
+
+            RelativePanel.SetAlignLeftWithPanel(rect1, true);
+            RelativePanel.SetBelow(rect2, rect1);
+            target.Measure(new Size(400, 400));
+            target.Arrange(new Rect(target.DesiredSize));
+
+            Assert.Equal(new Size(20, 40), target.Bounds.Size);
+            Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds);
+            Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds);
+        }
     }
 }