From 09a58d0c146a45a14ebf7acfa4e0bf7e4699b91a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 9 Jul 2020 12:58:39 -0300 Subject: [PATCH 01/15] realtive panel no longer cares about the declaration order. and measures to the largest child when not stretched. --- src/Avalonia.Controls/RelativePanel.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 033a5559f5..07137e3d1a 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -16,12 +16,16 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - foreach (var child in Children) + var maxSize = new Size(); + + foreach (var child in Children.OfType()) { - child?.Measure(availableSize); + child.Measure(availableSize); + maxSize = maxSize.WithWidth(Math.Max(maxSize.Width, child.DesiredSize.Width)); + maxSize = maxSize.WithHeight(Math.Max(maxSize.Height, child.DesiredSize.Height)); } - return availableSize; + return maxSize; } protected override Size ArrangeOverride(Size arrangeSize) @@ -183,11 +187,14 @@ namespace Avalonia.Controls _nodeDic.Clear(); } - public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null); + public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null, null); - private bool CheckCyclic(IEnumerable nodes, HashSet? set) + private bool CheckCyclic(IEnumerable nodes, GraphNode? waitNode, HashSet? set) { - set ??= new HashSet(); + if (set == null) + { + set = new HashSet(); + } foreach (var node in nodes) { @@ -206,9 +213,13 @@ namespace Avalonia.Controls if (!set.Add(node.Element)) return true; - return CheckCyclic(node.OutgoingNodes, set); + return CheckCyclic(node.OutgoingNodes, node.Arranged ? null : node, set); } + if (waitNode != null) + { + ArrangeChild(waitNode); + } return false; } From 8176ef66481256e11dead37e0163e590e0971121 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 19:11:19 +0200 Subject: [PATCH 02/15] Added failing test. Items are being materialized twice when not using virtualization. --- .../Presenters/ItemsPresenterTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs index fddc02f19c..fab57cec49 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs @@ -60,6 +60,25 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.IsType(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() { From 68792668ada4ad6cc2ecb10ada5fc6451d0779ac Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 13 Jul 2020 19:23:00 +0200 Subject: [PATCH 03/15] Make items be materialized only once. When virtualization was turned off in an `ItemsControl`, items were virtualized twice. on control creation. Fix that. --- src/Avalonia.Controls/Presenters/CarouselPresenter.cs | 5 +++++ src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index 70a7583daf..7888249bdd 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/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)); + } + /// /// Moves to the selected page, animating if a is set. /// diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index 23846bcd2e..52f173fc71 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -229,8 +229,6 @@ namespace Avalonia.Controls.Presenters } PanelCreated(Panel); - - ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// From fca777bf91794d253165c07d554698a5e6629854 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 13 Jul 2020 18:21:45 -0300 Subject: [PATCH 04/15] fixes for relativepanel --- src/Avalonia.Controls/RelativePanel.cs | 541 +++++++++++++++++-------- 1 file changed, 374 insertions(+), 167 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 07137e3d1a..a5743f4153 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -8,35 +8,61 @@ using Avalonia.Layout; namespace Avalonia.Controls { + public static partial class Extensions + { + /// + /// Returns a value that indicates whether the specified value is not a number (). + /// + /// A double-precision floating-point number. + /// true if evaluates to ; otherwise, false. + public static bool IsNaN(this double d) + { + return double.IsNaN(d); + } + + public static IEnumerable Do(this IEnumerable source, Action predicate) + { + var enumerable = source as IList ?? source.ToList(); + foreach (var item in enumerable) + { + predicate.Invoke(item); + } + + return enumerable; + } + } + public partial class RelativePanel : Panel { private readonly Graph _childGraph; public RelativePanel() => _childGraph = new Graph(); + - protected override Size MeasureOverride(Size availableSize) + private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { - var maxSize = new Size(); + var dependency = child.GetValue(property); - foreach (var child in Children.OfType()) + if (dependency is Layoutable layoutable) { - child.Measure(availableSize); - maxSize = maxSize.WithWidth(Math.Max(maxSize.Width, child.DesiredSize.Width)); - maxSize = maxSize.WithHeight(Math.Max(maxSize.Height, child.DesiredSize.Height)); + if (Children.Contains((ILayoutable)layoutable)) + return layoutable; + + throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}"); } - return maxSize; + return null; } - protected override Size ArrangeOverride(Size arrangeSize) + protected override Size MeasureOverride(Size availableSize) { - _childGraph.Reset(arrangeSize); + #region Calc DesiredSize - foreach (var child in Children.OfType()) + _childGraph.Clear(); + foreach (Layoutable child in Children) { if (child == null) continue; - var node = _childGraph.AddNode(child); node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child)); @@ -50,106 +76,175 @@ namespace Avalonia.Controls node.BelowNode = _childGraph.AddLink(node, GetDependencyElement(BelowProperty, child)); node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child)); - node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, 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(); + #endregion - foreach (var child in Children) - { - if (child.Bounds.Bottom > size.Height) - { - size = size.WithHeight(child.Bounds.Bottom); - } + #region Calc AvailableSize - if (child.Bounds.Right > size.Width) - { - size = size.WithWidth(child.Bounds.Right); - } - } + _childGraph.Reset(); + var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN()); + _childGraph.Reset(); + _childGraph.Measure(boundingSize); + return boundingSize; - if (VerticalAlignment == VerticalAlignment.Stretch) - { - size = size.WithHeight(arrangeSize.Height); - } - - if (HorizontalAlignment == HorizontalAlignment.Stretch) - { - size = size.WithWidth(arrangeSize.Width); - } + #endregion + } - return size; + protected override Size ArrangeOverride(Size arrangeSize) + { + _childGraph.GetNodes().Do(node => node.Arrange(arrangeSize)); + return arrangeSize; } - private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) + private class GraphNode { - var dependency = child.GetValue(property); + public bool Measured { get; set; } - if (dependency is Layoutable layoutable) - { - if (Children.Contains((ILayoutable)layoutable)) - return layoutable; + public Layoutable Element { get; } - throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}"); - } + private bool HorizontalOffsetFlag { get; set; } - return null; - } + private bool VerticalOffsetFlag { get; set; } - private class GraphNode - { - public Point Position { get; set; } + private Size BoundingSize { get; set; } - public bool Arranged { get; set; } + public Size OriginDesiredSize { get; set; } - public Layoutable Element { get; } + public double Left { get; set; } = double.NaN; + + public double Top { get; set; } = double.NaN; + + public double Right { get; set; } = double.NaN; + + public double Bottom { get; set; } = double.NaN; public HashSet OutgoingNodes { get; } - public GraphNode? AlignLeftWithNode { get; set; } + public GraphNode AlignLeftWithNode { get; set; } - public GraphNode? AlignTopWithNode { get; set; } + public GraphNode AlignTopWithNode { get; set; } - public GraphNode? AlignRightWithNode { get; set; } + public GraphNode AlignRightWithNode { get; set; } - public GraphNode? AlignBottomWithNode { get; set; } + public GraphNode AlignBottomWithNode { get; set; } - public GraphNode? LeftOfNode { get; set; } + public GraphNode LeftOfNode { get; set; } - public GraphNode? AboveNode { get; set; } + public GraphNode AboveNode { get; set; } - public GraphNode? RightOfNode { get; set; } + public GraphNode RightOfNode { get; set; } - public GraphNode? BelowNode { get; set; } + public GraphNode BelowNode { get; set; } - public GraphNode? AlignHorizontalCenterWith { get; set; } + public GraphNode AlignHorizontalCenterWith { get; set; } - public GraphNode? AlignVerticalCenterWith { get; set; } + public GraphNode AlignVerticalCenterWith { get; set; } public GraphNode(Layoutable element) { OutgoingNodes = new HashSet(); 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 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 _nodeDic; - private Size _arrangeSize; + private Size AvailableSize { get; set; } - public Graph() + public Graph() => _nodeDic = new Dictionary(); + + public IEnumerable GetNodes() => _nodeDic.Values; + + public void Clear() { - _nodeDic = new Dictionary(); + AvailableSize = new Size(); + _nodeDic.Clear(); } - public GraphNode? AddLink(GraphNode from, Layoutable? to) + public void Reset() => _nodeDic.Values.Do(node => node.Reset()); + + public GraphNode AddLink(GraphNode from, Layoutable to) { if (to == null) return null; @@ -181,183 +276,295 @@ 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, null); - - private bool CheckCyclic(IEnumerable nodes, GraphNode? waitNode, HashSet? set) + private void Measure(IEnumerable nodes, HashSet set) { - if (set == null) - { - set = new HashSet(); - } + set ??= new HashSet(); 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, node.Arranged ? null : node, set); - } + // 没有循环,且有依赖,则继续往下 + Measure(node.OutgoingNodes, set); - if (waitNode != null) - { - ArrangeChild(waitNode); + if (!node.Measured) + { + MeasureChild(node); + } } - return false; } - 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); + + #region Panel alignment + + if (alignLeftWithPanel) + node.Left = 0; + if (alignTopWithPanel) + node.Top = 0; + if (alignRightWithPanel) + node.Right = 0; + if (alignBottomWithPanel) + node.Bottom = 0; + + #endregion + + #region Sibling alignment + + 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); - } + #endregion + + #region Measure + + 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; } + } - if (node.AlignHorizontalCenterWith != null) + var availableWidth = AvailableSize.Width - node.Left - node.Right; + if (availableWidth.IsNaN()) + { + availableWidth = AvailableSize.Width; + + 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; + + #endregion - if (node.AlignLeftWithNode != null) + #region Sibling positional + + 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.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) + #endregion + + #region Sibling-center alignment + + 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) + #endregion + + #region Panel-center alignment + + 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; + } + + #endregion + + 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; } } } From c96f328d0633cdb27d126ac965fa746d1b3c999e Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Tue, 14 Jul 2020 07:49:39 +0300 Subject: [PATCH 05/15] Fix typo in PointerReleasedEventMessage --- src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index f5c673d5f9..931c27c575 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/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(() => { From 1c8541c06011f7827bf61aa5227ea894854b6f34 Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Tue, 14 Jul 2020 11:52:04 +0300 Subject: [PATCH 06/15] fix typo & add missing attribute --- src/Avalonia.Controls/RelativePanel.AttachedProperties.cs | 2 +- tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index f93de5ca15..f64c26682b 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls AlignVerticalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); BelowProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); - LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + RightOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); } /// diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 4248e643eb..99991cca02 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/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 }; From 9f6c52711ef6e2cbd6c23634c869acfb914b8348 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 10:59:46 -0300 Subject: [PATCH 07/15] add more relativepanel tests. --- .../RelativePanelTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index 4248e643eb..7b1f6d07b7 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/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); + } } } From 0c365b837d030896a8503298db78e19e275ba738 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 11:02:04 -0300 Subject: [PATCH 08/15] fix relativepanel --- src/Avalonia.Controls/RelativePanel.AttachedProperties.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index f93de5ca15..f64c26682b 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls AlignVerticalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); BelowProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); - LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + RightOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); } /// From cdd8df383eb6c850b93e470fcf8292a7143e87bf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 11:02:18 -0300 Subject: [PATCH 09/15] make utility methods internal. --- src/Avalonia.Controls/RelativePanel.cs | 54 +++++++++++++------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index a5743f4153..c50817f460 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -8,36 +8,12 @@ using Avalonia.Layout; namespace Avalonia.Controls { - public static partial class Extensions - { - /// - /// Returns a value that indicates whether the specified value is not a number (). - /// - /// A double-precision floating-point number. - /// true if evaluates to ; otherwise, false. - public static bool IsNaN(this double d) - { - return double.IsNaN(d); - } - - public static IEnumerable Do(this IEnumerable source, Action predicate) - { - var enumerable = source as IList ?? source.ToList(); - foreach (var item in enumerable) - { - predicate.Invoke(item); - } - - return enumerable; - } - } - public partial class RelativePanel : Panel { private readonly Graph _childGraph; public RelativePanel() => _childGraph = new Graph(); - + private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { @@ -76,7 +52,7 @@ namespace Avalonia.Controls node.BelowNode = _childGraph.AddLink(node, GetDependencyElement(BelowProperty, child)); node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child)); - node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); + node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); } _childGraph.Measure(availableSize); @@ -207,7 +183,7 @@ namespace Avalonia.Controls prevSize = prevSize.WithHeight(prevSize.Height + node.BoundingSize.Height); if (GetAlignVerticalCenterWithPanel(node.Element) || node.VerticalOffsetFlag) { - prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height); + prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height); prevNode.VerticalOffsetFlag = true; } if (node.HorizontalOffsetFlag) @@ -568,4 +544,28 @@ namespace Avalonia.Controls } } } + + internal static partial class Extensions + { + /// + /// Returns a value that indicates whether the specified value is not a number (). + /// + /// A double-precision floating-point number. + /// true if evaluates to ; otherwise, false. + public static bool IsNaN(this double d) + { + return double.IsNaN(d); + } + + public static IEnumerable Do(this IEnumerable source, Action predicate) + { + var enumerable = source as IList ?? source.ToList(); + foreach (var item in enumerable) + { + predicate.Invoke(item); + } + + return enumerable; + } + } } From 70c71263ae8841d81121a13902723ec62e2bd014 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 11:04:34 -0300 Subject: [PATCH 10/15] fix nullable implementation. --- src/Avalonia.Controls/RelativePanel.cs | 27 +++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index c50817f460..e0a47b056b 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/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,7 +14,6 @@ namespace Avalonia.Controls public RelativePanel() => _childGraph = new Graph(); - private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { var dependency = child.GetValue(property); @@ -100,25 +99,25 @@ namespace Avalonia.Controls public HashSet OutgoingNodes { get; } - public GraphNode AlignLeftWithNode { get; set; } + public GraphNode? AlignLeftWithNode { get; set; } - public GraphNode AlignTopWithNode { get; set; } + public GraphNode? AlignTopWithNode { get; set; } - public GraphNode AlignRightWithNode { get; set; } + public GraphNode? AlignRightWithNode { get; set; } - public GraphNode AlignBottomWithNode { get; set; } + public GraphNode? AlignBottomWithNode { get; set; } - public GraphNode LeftOfNode { get; set; } + public GraphNode? LeftOfNode { get; set; } - public GraphNode AboveNode { get; set; } + public GraphNode? AboveNode { get; set; } - public GraphNode RightOfNode { get; set; } + public GraphNode? RightOfNode { get; set; } - public GraphNode BelowNode { get; set; } + public GraphNode? BelowNode { get; set; } - public GraphNode AlignHorizontalCenterWith { get; set; } + public GraphNode? AlignHorizontalCenterWith { get; set; } - public GraphNode AlignVerticalCenterWith { get; set; } + public GraphNode? AlignVerticalCenterWith { get; set; } public GraphNode(Layoutable element) { @@ -220,7 +219,7 @@ namespace Avalonia.Controls public void Reset() => _nodeDic.Values.Do(node => node.Reset()); - public GraphNode AddLink(GraphNode from, Layoutable to) + public GraphNode? AddLink(GraphNode from, Layoutable? to) { if (to == null) return null; @@ -258,7 +257,7 @@ namespace Avalonia.Controls Measure(_nodeDic.Values, null); } - private void Measure(IEnumerable nodes, HashSet set) + private void Measure(IEnumerable nodes, HashSet? set) { set ??= new HashSet(); From 0001578f0cc4d5c0fb2c16249b0900f150aace34 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 11:09:46 -0300 Subject: [PATCH 11/15] remove regions. --- src/Avalonia.Controls/RelativePanel.cs | 32 -------------------------- 1 file changed, 32 deletions(-) diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index e0a47b056b..a3ad30db76 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -31,8 +31,6 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - #region Calc DesiredSize - _childGraph.Clear(); foreach (Layoutable child in Children) { @@ -56,17 +54,11 @@ namespace Avalonia.Controls } _childGraph.Measure(availableSize); - #endregion - - #region Calc AvailableSize - _childGraph.Reset(); var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN()); _childGraph.Reset(); _childGraph.Measure(boundingSize); return boundingSize; - - #endregion } protected override Size ArrangeOverride(Size arrangeSize) @@ -305,8 +297,6 @@ namespace Avalonia.Controls var alignRightWithPanel = GetAlignRightWithPanel(child); var alignBottomWithPanel = GetAlignBottomWithPanel(child); - #region Panel alignment - if (alignLeftWithPanel) node.Left = 0; if (alignTopWithPanel) @@ -316,10 +306,6 @@ namespace Avalonia.Controls if (alignBottomWithPanel) node.Bottom = 0; - #endregion - - #region Sibling alignment - if (node.AlignLeftWithNode != null) { node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5; @@ -344,10 +330,6 @@ namespace Avalonia.Controls : node.AlignBottomWithNode.Bottom * 0.5; } - #endregion - - #region Measure - var availableHeight = AvailableSize.Height - node.Top - node.Bottom; if (availableHeight.IsNaN()) { @@ -381,10 +363,6 @@ namespace Avalonia.Controls child.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0))); var childSize = child.DesiredSize; - #endregion - - #region Sibling positional - if (node.LeftOfNode != null && node.Left.IsNaN()) { node.Left = node.LeftOfNode.Left - childSize.Width; @@ -421,10 +399,6 @@ namespace Avalonia.Controls } } - #endregion - - #region Sibling-center alignment - if (node.AlignHorizontalCenterWith != null) { var halfWidthLeft = (AvailableSize.Width + node.AlignHorizontalCenterWith.Left - node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5; @@ -457,10 +431,6 @@ namespace Avalonia.Controls node.Bottom = (node.Bottom + halfHeightBottom) * 0.5; } - #endregion - - #region Panel-center alignment - if (GetAlignHorizontalCenterWithPanel(child)) { var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5; @@ -491,8 +461,6 @@ namespace Avalonia.Controls node.Bottom = (node.Bottom + halfSubHeight) * 0.5; } - #endregion - if (node.Left.IsNaN()) { if (!node.Right.IsNaN()) From 3fb97afa6d88431812e0398d29eb03127b667d8d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 13:33:35 -0300 Subject: [PATCH 12/15] make toggle switch use verticalcontentalignment center by default. --- src/Avalonia.Themes.Default/ToggleSwitch.xaml | 3 ++- src/Avalonia.Themes.Fluent/ToggleSwitch.xaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index 893d64f505..ded121f5f6 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -43,7 +43,8 @@ - + + diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index e7f8fb1641..4309edefe3 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -43,7 +43,8 @@ - + + From 162583b2decfd264c6e4be9661d31209045eb36e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 14:30:31 -0300 Subject: [PATCH 13/15] add a failing unit test. --- .../ApplicationTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index e533001242..d485d424fb 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Reactive.Subjects; +using Avalonia.Data; using Avalonia.Threading; using Avalonia.UnitTests; using Xunit; @@ -32,5 +34,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); + } + } } } From f1eae6ce1289140063f36f8c86800f3d2cf2a314 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 14:31:00 -0300 Subject: [PATCH 14/15] correctly cast to IDataContextProvider. --- src/Markup/Avalonia.Markup/Data/BindingBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index 7c4e7b5efe..3dbc83a7df 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -137,9 +137,9 @@ namespace Avalonia.Data { Contract.Requires(target != null); - if (!(target is IStyledElement)) + if (!(target is IDataContextProvider)) { - target = anchor as IStyledElement; + target = anchor as IDataContextProvider; if (target == null) { From a5499a908d8a7d740fc2873ce16298c08c34deae Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 14 Jul 2020 14:33:45 -0300 Subject: [PATCH 15/15] remove usings. --- tests/Avalonia.Controls.UnitTests/ApplicationTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index d485d424fb..58ddc8ca60 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Reactive.Subjects; using Avalonia.Data; -using Avalonia.Threading; using Avalonia.UnitTests; using Xunit;