diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index 1524626133..91a4f15039 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls static RelativePanel() { ClipToBoundsProperty.OverrideDefaultValue(true); - + AboveProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); AlignBottomWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); AlignBottomWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); @@ -61,7 +61,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AboveProperty = AvaloniaProperty.RegisterAttached("Above", typeof(RelativePanel)); @@ -126,7 +126,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignBottomWithProperty = AvaloniaProperty.RegisterAttached("AlignBottomWith", typeof(RelativePanel)); @@ -190,7 +190,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignHorizontalCenterWithProperty = AvaloniaProperty.RegisterAttached("AlignHorizontalCenterWith", typeof(object), typeof(RelativePanel)); @@ -255,7 +255,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignLeftWithProperty = AvaloniaProperty.RegisterAttached("AlignLeftWith"); @@ -320,7 +320,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignRightWithProperty = AvaloniaProperty.RegisterAttached("AlignRightWith"); @@ -381,7 +381,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty AlignTopWithProperty = AvaloniaProperty.RegisterAttached("AlignTopWith"); @@ -434,7 +434,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to align this element's horizontal center with.) - + public static void SetAlignVerticalCenterWith(AvaloniaObject obj, object value) { obj.SetValue(AlignVerticalCenterWithProperty, value); @@ -465,7 +465,7 @@ namespace Avalonia.Controls /// /// The object to which the property value is written. /// The value to set. (The element to position this element below.) - + public static void SetBelow(AvaloniaObject obj, object value) { obj.SetValue(BelowProperty, value); @@ -474,7 +474,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty BelowProperty = AvaloniaProperty.RegisterAttached("Below"); @@ -505,7 +505,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty LeftOfProperty = AvaloniaProperty.RegisterAttached("LeftOf"); @@ -536,7 +536,7 @@ namespace Avalonia.Controls /// /// Identifies the XAML attached property. /// - + public static readonly AttachedProperty RightOfProperty = AvaloniaProperty.RegisterAttached("RightOf"); } diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 73f668bbe9..d44d39ed90 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -1,470 +1,354 @@ -using System; +/* Ported from https://github.com/ghost1372/HandyControls/blob/develop/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs */ +using System; using System.Collections.Generic; using System.Linq; using Avalonia.Layout; namespace Avalonia.Controls { - /// - /// Defines an area within which you can position and align child objects in relation - /// to each other or the parent panel. - /// - /// - /// Default position - /// By default, any unconstrained element declared as a child of the RelativePanel is given the entire - /// available space and positioned at the(0, 0) coordinates(upper left corner) of the panel.So, if you - /// are positioning a second element relative to an unconstrained element, keep in mind that the second - /// element might get pushed out of the panel. - /// - ///Conflicting relationships - /// - /// If you set multiple relationships that target the same edge of an element, you might have conflicting - /// relationships in your layout as a result.When this happens, the relationships are applied in the - /// following order of priority: - /// • Panel alignment relationships (AlignTopWithPanel, AlignLeftWithPanel, …) are applied first. - /// • Sibling alignment relationships(AlignTopWith, AlignLeftWith, …) are applied second. - /// • Sibling positional relationships(Above, Below, RightOf, LeftOf) are applied last. - /// - /// - /// The panel-center alignment properties(AlignVerticalCenterWith, AlignHorizontalCenterWithPanel, ...) are - /// typically used independently of other constraints and are applied if there is no conflict. - /// - /// - /// The HorizontalAlignment and VerticalAlignment properties on UI elements are applied after relationship - /// properties are evaluated and applied. These properties control the placement of the element within the - /// available size for the element, if the desired size is smaller than the available size. - /// - /// public partial class RelativePanel : Panel { - // Dependency property for storing intermediate arrange state on the children - private static readonly StyledProperty ArrangeStateProperty = - AvaloniaProperty.Register("ArrangeState"); - - /// - /// When overridden in a derived class, measures the size in layout required for - /// child elements and determines a size for the System.Windows.FrameworkElement-derived - /// class. - /// - /// The available size that this element can give to child elements. Infinity can - /// be specified as a value to indicate that the element will size to whatever content - /// is available. - /// - /// - /// The size that this element determines it needs during layout, based on its calculations - /// of child element sizes. - /// + private readonly Graph _childGraph; + + public RelativePanel() => _childGraph = new Graph(); + protected override Size MeasureOverride(Size availableSize) { - foreach (var child in Children.OfType()) + foreach (var child in Children) { - child.Measure(availableSize); + child?.Measure(availableSize); } - Rect bounds = new Rect(); + return availableSize; + } + + protected override Size ArrangeOverride(Size arrangeSize) + { + _childGraph.Reset(arrangeSize); + + foreach (var child in Children.OfType()) + { + if (child == null) + continue; + + var node = _childGraph.AddNode(child); + + node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child)); + node.AlignTopWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignTopWithProperty, child)); + node.AlignRightWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignRightWithProperty, child)); + node.AlignBottomWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignBottomWithProperty, child)); + + node.LeftOfNode = _childGraph.AddLink(node, GetDependencyElement(LeftOfProperty, child)); + node.AboveNode = _childGraph.AddLink(node, GetDependencyElement(AboveProperty, child)); + node.RightOfNode = _childGraph.AddLink(node, GetDependencyElement(RightOfProperty, child)); + node.BelowNode = _childGraph.AddLink(node, GetDependencyElement(BelowProperty, child)); + + node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child)); + node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); + } - foreach (var item in CalculateLocations(availableSize)) + if (_childGraph.CheckCyclic()) { - if (item.Item2.Size.Width < item.Item1.DesiredSize.Width || item.Item2.Size.Height < item.Item1.DesiredSize.Height) - item.Item1.Measure(item.Item2.Size); + throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); + } + + var size = new Size(); - if(item.Item2.Right > bounds.Width) + foreach (var child in Children) + { + if (child.Bounds.Bottom > size.Height) { - bounds = bounds.WithWidth(item.Item2.Right); + size = size.WithHeight(child.Bounds.Bottom); } - if(item.Item2.Bottom > bounds.Height) + if (child.Bounds.Right > size.Width) { - bounds = bounds.WithHeight(item.Item2.Bottom); + size = size.WithWidth(child.Bounds.Right); } } - return bounds.Size; + if (VerticalAlignment == VerticalAlignment.Stretch) + { + size = size.WithHeight(arrangeSize.Height); + } + + if (HorizontalAlignment == HorizontalAlignment.Stretch) + { + size = size.WithWidth(arrangeSize.Width); + } + + return size; } - /// - /// When overridden in a derived class, positions child elements and determines a - /// size for a System.Windows.FrameworkElement derived class. - /// - /// - /// The final area within the parent that this element should use to arrange itself - /// and its children. - /// - /// The actual size used. - protected override Size ArrangeOverride(Size finalSize) + private Layoutable GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { - Rect bounds = new Rect(); + var dependency = child.GetValue(property); - foreach (var item in CalculateLocations(finalSize)) + if (dependency is Layoutable layoutable) { - item.Item1.Arrange(item.Item2); + if (Children.Contains((ILayoutable)layoutable)) + return layoutable; - if (item.Item2.Right > bounds.Width) - { - bounds = bounds.WithWidth(item.Item2.Right); - } + throw new ArgumentException(string.Format("RelativePanel error: Element does not exist in the current context", property.Name)); + } - if (item.Item2.Bottom > bounds.Height) - { - bounds = bounds.WithHeight(item.Item2.Bottom); - } - } - - return bounds.Size; + return null; } - private IEnumerable> CalculateLocations(Size finalSize) + private class GraphNode { - //List of margins for each element between the element and panel (left, top, right, bottom) - List arranges = new List(Children.Count); - //First pass aligns all sides that aren't constrained by other elements - int arrangedCount = 0; - foreach (var child in Children.OfType()) + public Point Position { get; set; } + + public bool Arranged { get; set; } + + public Layoutable Element { get; } + + public HashSet OutgoingNodes { get; } + + public GraphNode AlignLeftWithNode { get; set; } + + public GraphNode AlignTopWithNode { get; set; } + + public GraphNode AlignRightWithNode { get; set; } + + public GraphNode AlignBottomWithNode { get; set; } + + public GraphNode LeftOfNode { get; set; } + + public GraphNode AboveNode { get; set; } + + public GraphNode RightOfNode { get; set; } + + public GraphNode BelowNode { get; set; } + + public GraphNode AlignHorizontalCenterWith { get; set; } + + public GraphNode AlignVerticalCenterWith { get; set; } + + public GraphNode(Layoutable element) + { + OutgoingNodes = new HashSet(); + Element = element; + } + } + + private class Graph + { + private readonly Dictionary _nodeDic; + + private Size _arrangeSize; + + public Graph() { - //NaN means the arrange value is not constrained yet for that side - double[] rect = new[] { double.NaN, double.NaN, double.NaN, double.NaN }; - arranges.Add(rect); - child.SetValue(ArrangeStateProperty, rect); + _nodeDic = new Dictionary(); + } - //Align with panels always wins, so do these first, or if no constraints are set at all set margin to 0 + public GraphNode AddLink(GraphNode from, Layoutable to) + { + if (to == null) + return null; - //Left side - if (GetAlignLeftWithPanel(child)) + GraphNode nodeTo; + if (_nodeDic.ContainsKey(to)) { - rect[0] = 0; + nodeTo = _nodeDic[to]; } - else if ( - child.GetValue(AlignLeftWithProperty) == null && - child.GetValue(RightOfProperty) == null && - child.GetValue(AlignHorizontalCenterWithProperty) == null && - !GetAlignHorizontalCenterWithPanel(child)) + else { - if (GetAlignRightWithPanel(child)) - rect[0] = finalSize.Width - child.DesiredSize.Width; - else if (child.GetValue(AlignRightWithProperty) == null && child.GetValue(AlignHorizontalCenterWithProperty) == null && child.GetValue(LeftOfProperty) == null) - rect[0] = 0; //default fallback to 0 + nodeTo = new GraphNode(to); + _nodeDic[to] = nodeTo; } - //Top side - if (GetAlignTopWithPanel(child)) + from.OutgoingNodes.Add(nodeTo); + return nodeTo; + } + + public GraphNode AddNode(Layoutable value) + { + if (!_nodeDic.ContainsKey(value)) { - rect[1] = 0; + var node = new GraphNode(value); + _nodeDic.Add(value, node); + return node; } - else if ( - child.GetValue(AlignTopWithProperty) == null && - child.GetValue(BelowProperty) == null && - child.GetValue(AlignVerticalCenterWithProperty) == null && - !GetAlignVerticalCenterWithPanel(child)) + + return _nodeDic[value]; + } + + public void Reset(Size arrangeSize) + { + _arrangeSize = arrangeSize; + _nodeDic.Clear(); + } + + public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null); + + private bool CheckCyclic(IEnumerable nodes, HashSet set) + { + if (set == null) { - if (GetAlignBottomWithPanel(child)) - rect[1] = finalSize.Height - child.DesiredSize.Height; - else if (child.GetValue(AlignBottomWithProperty) == null && child.GetValue(AlignVerticalCenterWithProperty) == null && child.GetValue(AboveProperty) == null) - rect[1] = 0; //default fallback to 0 + set = new HashSet(); } - //Right side - if (GetAlignRightWithPanel(child)) + foreach (var node in nodes) { - rect[2] = 0; + if (!node.Arranged && node.OutgoingNodes.Count == 0) + { + ArrangeChild(node, true); + continue; + } + + if (node.OutgoingNodes.All(item => item.Arranged)) + { + ArrangeChild(node); + continue; + } + + if (!set.Add(node.Element)) + return true; + + return CheckCyclic(node.OutgoingNodes, set); } - else if (!double.IsNaN(rect[0]) && - child.GetValue(AlignRightWithProperty) == null && - child.GetValue(LeftOfProperty) == null && - child.GetValue(AlignHorizontalCenterWithProperty) == null && - !GetAlignHorizontalCenterWithPanel(child)) + + return false; + } + + private void ArrangeChild(GraphNode node, bool ignoneSibling = false) + { + var child = node.Element; + var childSize = child.DesiredSize; + var childPos = new Point(); + + if (GetAlignHorizontalCenterWithPanel(child)) { - rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; + childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2); } - //Bottom side - if (GetAlignBottomWithPanel(child)) - rect[3] = 0; - else if (!double.IsNaN(rect[1]) && - (child.GetValue(AlignBottomWithProperty) == null && - child.GetValue(AboveProperty) == null) && - child.GetValue(AlignVerticalCenterWithProperty) == null && - !GetAlignVerticalCenterWithPanel(child)) + if (GetAlignVerticalCenterWithPanel(child)) { - rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; + childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2); } - if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && - !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) - arrangedCount++; - } - int i = 0; - //Run iterative layout passes - while (arrangedCount < Children.Count) - { - bool valueChanged = false; - i = 0; - foreach (var child in Children.OfType()) + var alignLeftWithPanel = GetAlignLeftWithPanel(child); + var alignTopWithPanel = GetAlignTopWithPanel(child); + var alignRightWithPanel = GetAlignRightWithPanel(child); + var alignBottomWithPanel = GetAlignBottomWithPanel(child); + + if (!ignoneSibling) { - double[] rect = arranges[i++]; + if (node.LeftOfNode != null) + { + childPos = childPos.WithX(node.LeftOfNode.Position.X - childSize.Width); + } - if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && - !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) - continue; //Control is fully arranged + if (node.AboveNode != null) + { + childPos = childPos.WithY(node.AboveNode.Position.Y - childSize.Height); + } - //Calculate left side - if (double.IsNaN(rect[0])) + if (node.RightOfNode != null) { - var alignLeftWith = GetDependencyElement(RelativePanel.AlignLeftWithProperty, child); - if (alignLeftWith != null) - { - double[] r = (double[])alignLeftWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[0])) - { - rect[0] = r[0]; - valueChanged = true; - } - } - else - { - var rightOf = GetDependencyElement(RelativePanel.RightOfProperty, child); - if (rightOf != null) - { - double[] r = (double[])rightOf.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[2])) - { - rect[0] = finalSize.Width - r[2]; - valueChanged = true; - } - } - else if (!double.IsNaN(rect[2])) - { - rect[0] = finalSize.Width - rect[2] - child.DesiredSize.Width; - valueChanged = true; - } - else - { - rect[0] = 0; - } - } + childPos = childPos.WithX(node.RightOfNode.Position.X + node.RightOfNode.Element.DesiredSize.Width); } - //Calculate top side - if (double.IsNaN(rect[1])) + + if (node.BelowNode != null) { - var alignTopWith = GetDependencyElement(RelativePanel.AlignTopWithProperty, child); - if (alignTopWith != null) - { - double[] r = (double[])alignTopWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[1])) - { - rect[1] = r[1]; - valueChanged = true; - } - } - else - { - var below = GetDependencyElement(RelativePanel.BelowProperty, child); - if (below != null) - { - double[] r = (double[])below.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[3])) - { - rect[1] = finalSize.Height - r[3]; - valueChanged = true; - } - } - else if (!double.IsNaN(rect[3])) - { - rect[1] = finalSize.Height - rect[3] - child.DesiredSize.Height; - valueChanged = true; - } - else - { - rect[1] = 0; - } - } + childPos = childPos.WithY(node.BelowNode.Position.Y + node.BelowNode.Element.DesiredSize.Height); } - //Calculate right side - if (double.IsNaN(rect[2])) + + if (node.AlignHorizontalCenterWith != null) { - var alignRightWith = GetDependencyElement(RelativePanel.AlignRightWithProperty, child); - if (alignRightWith != null) - { - double[] r = (double[])alignRightWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[2])) - { - rect[2] = r[2]; - if (double.IsNaN(rect[0])) - { - if (child.GetValue(RelativePanel.AlignLeftWithProperty) == null) - { - rect[0] = rect[2] + child.DesiredSize.Width; - valueChanged = true; - } - } - } - } - else - { - var leftOf = GetDependencyElement(RelativePanel.LeftOfProperty, child); - if (leftOf != null) - { - double[] r = (double[])leftOf.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[0])) - { - rect[2] = finalSize.Width - r[0]; - valueChanged = true; - } - } - else if (!double.IsNaN(rect[0])) - { - rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; - valueChanged = true; - } - else - { - rect[2] = child.DesiredSize.Width; - } - } + childPos = childPos.WithX(node.AlignHorizontalCenterWith.Position.X + + (node.AlignHorizontalCenterWith.Element.DesiredSize.Width - childSize.Width) / 2); } - //Calculate bottom side - if (double.IsNaN(rect[3])) + + if (node.AlignVerticalCenterWith != null) { - var alignBottomWith = GetDependencyElement(RelativePanel.AlignBottomWithProperty, child); - if (alignBottomWith != null) - { - double[] r = (double[])alignBottomWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[3])) - { - rect[3] = r[3]; - valueChanged = true; - if (double.IsNaN(rect[1])) - { - if (child.GetValue(RelativePanel.AlignTopWithProperty) == null) - { - rect[1] = finalSize.Height - rect[3] - child.DesiredSize.Height; - } - } - } - } - else - { - var above = GetDependencyElement(RelativePanel.AboveProperty, child); - if (above != null) - { - double[] r = (double[])above.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[1])) - { - rect[3] = finalSize.Height - r[1]; - valueChanged = true; - } - } - else if (!double.IsNaN(rect[1])) - { - rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; - valueChanged = true; - } - else - { - rect[3] = child.DesiredSize.Height; - } - } + childPos = childPos.WithY(node.AlignVerticalCenterWith.Position.Y + + (node.AlignVerticalCenterWith.Element.DesiredSize.Height - childSize.Height) / 2); } - //Calculate horizontal alignment - if (double.IsNaN(rect[0]) && double.IsNaN(rect[2])) + + if (node.AlignLeftWithNode != null) { - var alignHorizontalCenterWith = GetDependencyElement(RelativePanel.AlignHorizontalCenterWithProperty, child); - if (alignHorizontalCenterWith != null) - { - double[] r = (double[])alignHorizontalCenterWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[0]) && !double.IsNaN(r[2])) - { - rect[0] = r[0] + (finalSize.Width - r[2] - r[0]) * .5 - child.DesiredSize.Width * .5; - rect[2] = finalSize.Width - rect[0] - child.DesiredSize.Width; - valueChanged = true; - } - } - else - { - if (GetAlignHorizontalCenterWithPanel(child)) - { - var roomToSpare = finalSize.Width - child.DesiredSize.Width; - rect[0] = roomToSpare * .5; - rect[2] = roomToSpare * .5; - valueChanged = true; - } - } + childPos = childPos.WithX(node.AlignLeftWithNode.Position.X); } - //Calculate vertical alignment - if (double.IsNaN(rect[1]) && double.IsNaN(rect[3])) + if (node.AlignTopWithNode != null) { - var alignVerticalCenterWith = GetDependencyElement(RelativePanel.AlignVerticalCenterWithProperty, child); - if (alignVerticalCenterWith != null) - { - double[] r = (double[])alignVerticalCenterWith.GetValue(ArrangeStateProperty); - if (!double.IsNaN(r[1]) && !double.IsNaN(r[3])) - { - rect[1] = r[1] + (finalSize.Height - r[3] - r[1]) * .5 - child.DesiredSize.Height * .5; - rect[3] = finalSize.Height - rect[1] - child.DesiredSize.Height; - valueChanged = true; - } - } - else - { - if (GetAlignVerticalCenterWithPanel(child)) - { - var roomToSpare = finalSize.Height - child.DesiredSize.Height; - rect[1] = roomToSpare * .5; - rect[3] = roomToSpare * .5; - valueChanged = true; - } - } + childPos = childPos.WithY(node.AlignTopWithNode.Position.Y); } + if (node.AlignRightWithNode != null) + { + childPos = childPos.WithX(node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width); + } - //if panel is now fully arranged, increase the counter - if (!double.IsNaN(rect[0]) && !double.IsNaN(rect[1]) && - !double.IsNaN(rect[2]) && !double.IsNaN(rect[3])) - arrangedCount++; + if (node.AlignBottomWithNode != null) + { + childPos = childPos.WithY(node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height); + } } - if (!valueChanged) + + if (alignLeftWithPanel) { - //If a layout pass didn't increase number of arranged elements, - //there must be a circular dependency - throw new ArgumentException("RelativePanel error: Circular dependency detected. Layout could not complete"); + if (node.AlignRightWithNode != null) + { + childPos = childPos.WithX((node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width) / 2); + } + else + { + childPos = childPos.WithX(0); + } } - } - i = 0; - //Arrange iterations complete - Apply the results to the child elements - foreach (var child in Children.OfType()) - { - double[] rect = arranges[i++]; - //Measure child again with the new calculated available size - //this helps for instance textblocks to reflow the text wrapping - //We should probably have done this during the measure step but it would cause a more expensive - //measure+arrange layout cycle - //if(child is TextBlock) - // child.Measure(new Size(Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); - - //if(child is TextBlock tb) - //{ - // tb.ArrangeOverride(new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); - //} - //else - yield return new Tuple(child, new Rect(rect[0], rect[1], Math.Max(0, finalSize.Width - rect[2] - rect[0]), Math.Max(0, finalSize.Height - rect[3] - rect[1]))); - } - } + if (alignTopWithPanel) + { + if (node.AlignBottomWithNode != null) + { + childPos = childPos.WithY((node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height) / 2); + } + else + { + childPos = childPos.WithY(0); + } + } - //Gets the element that's referred to in the alignment attached properties - private Layoutable GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) - { - var dependency = child.GetValue(property); + if (alignRightWithPanel) + { + if (alignLeftWithPanel) + { + childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2); + } + else if (node.AlignLeftWithNode == null) + { + childPos = childPos.WithX(_arrangeSize.Width - childSize.Width); + } + else + { + childPos = childPos.WithX((_arrangeSize.Width + node.AlignLeftWithNode.Position.X - childSize.Width) / 2); + } + } - if (dependency is Layoutable layoutable) - { - if (Children.Contains((ILayoutable)layoutable)) - return layoutable; + if (alignBottomWithPanel) + { + if (alignTopWithPanel) + { + childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2); + } + else if (node.AlignTopWithNode == null) + { + childPos = childPos.WithY(_arrangeSize.Height - childSize.Height); + } + else + { + childPos = childPos.WithY((_arrangeSize.Height + node.AlignLeftWithNode.Position.Y - childSize.Height) / 2); + } + } - throw new ArgumentException(string.Format("RelativePanel error: Element does not exist in the current context", property.Name)); + child.Arrange(new Rect(childPos.X, childPos.Y, childSize.Width, childSize.Height)); + node.Position = childPos; + node.Arranged = true; } - - return null; } } } diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs index feb30200af..7a2036687e 100644 --- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs @@ -28,31 +28,5 @@ 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 (Skip = "TODO Implement auto sizing")] - public void Lays_Out_2nd_Child_Aligned_With_Panel_sits_inside_1st() - { - var rect1 = new Rectangle { Height = 50, Width = 200 }; - var rect2 = new Rectangle { Height = 20, Width = 20 }; - - var target = new RelativePanel - { - Children = - { - rect1, rect2 - }, - HorizontalAlignment = Layout.HorizontalAlignment.Left, - VerticalAlignment = Layout.VerticalAlignment.Top - }; - - RelativePanel.SetAlignLeftWithPanel(rect1, true); - RelativePanel.SetAlignBottomWithPanel(rect2, true); - target.Measure(new Size(400, 400)); - target.Arrange(new Rect(target.DesiredSize)); - - Assert.Equal(new Size(50, 200), target.Bounds.Size); - Assert.Equal(new Rect(0, 0, 50, 200), target.Children[0].Bounds); - Assert.Equal(new Rect(0, 30, 20, 20), target.Children[1].Bounds); - } } }