Browse Source

acceptable relativepanel implementation.

pull/3987/head
Dan Walmsley 6 years ago
parent
commit
4b9cf819ba
  1. 24
      src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
  2. 658
      src/Avalonia.Controls/RelativePanel.cs
  3. 26
      tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs

24
src/Avalonia.Controls/RelativePanel.AttachedProperties.cs

@ -14,7 +14,7 @@ namespace Avalonia.Controls
static RelativePanel()
{
ClipToBoundsProperty.OverrideDefaultValue<RelativePanel>(true);
AboveProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignBottomWithPanelProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
AlignBottomWithProperty.Changed.AddClassHandler<Layoutable>(OnAlignPropertiesChanged);
@ -61,7 +61,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.AboveProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AboveProperty =
AvaloniaProperty.RegisterAttached<Layoutable, object>("Above", typeof(RelativePanel));
@ -126,7 +126,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignBottomWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignBottomWithProperty =
AvaloniaProperty.RegisterAttached<Layoutable, object>("AlignBottomWith", typeof(RelativePanel));
@ -190,7 +190,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignHorizontalCenterWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignHorizontalCenterWithProperty =
AvaloniaProperty.RegisterAttached<Layoutable, object>("AlignHorizontalCenterWith", typeof(object), typeof(RelativePanel));
@ -255,7 +255,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignLeftWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignLeftWithProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignLeftWith");
@ -320,7 +320,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignRightWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignRightWithProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignRightWith");
@ -381,7 +381,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.AlignTopWithProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> AlignTopWithProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("AlignTopWith");
@ -434,7 +434,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to align this element's horizontal center with.)</param>
public static void SetAlignVerticalCenterWith(AvaloniaObject obj, object value)
{
obj.SetValue(AlignVerticalCenterWithProperty, value);
@ -465,7 +465,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="obj">The object to which the property value is written.</param>
/// <param name="value">The value to set. (The element to position this element below.)</param>
public static void SetBelow(AvaloniaObject obj, object value)
{
obj.SetValue(BelowProperty, value);
@ -474,7 +474,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.BelowProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> BelowProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("Below");
@ -505,7 +505,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.LeftOfProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> LeftOfProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("LeftOf");
@ -536,7 +536,7 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the <see cref="RelativePanel.RightOfProperty"/> XAML attached property.
/// </summary>
public static readonly AttachedProperty<object> RightOfProperty =
AvaloniaProperty.RegisterAttached<RelativePanel, Layoutable, object>("RightOf");
}

658
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
{
/// <summary>
/// Defines an area within which you can position and align child objects in relation
/// to each other or the parent panel.
/// </summary>
/// <remarks>
/// <para><b>Default position</b></para>
/// <para>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.
/// </para>
///<para><b>Conflicting relationships</b></para>
/// <para>
/// 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.
/// </para>
/// <para>
/// The panel-center alignment properties(AlignVerticalCenterWith, AlignHorizontalCenterWithPanel, ...) are
/// typically used independently of other constraints and are applied if there is no conflict.
///</para>
/// <para>
/// 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.
/// </para>
/// </remarks>
public partial class RelativePanel : Panel
{
// Dependency property for storing intermediate arrange state on the children
private static readonly StyledProperty<double[]> ArrangeStateProperty =
AvaloniaProperty.Register<RelativePanel, double[]>("ArrangeState");
/// <summary>
/// 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.</summary>
/// <param name="availableSize">
/// 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.
/// </param>
/// <returns>
/// The size that this element determines it needs during layout, based on its calculations
/// of child element sizes.
/// </returns>
private readonly Graph _childGraph;
public RelativePanel() => _childGraph = new Graph();
protected override Size MeasureOverride(Size availableSize)
{
foreach (var child in Children.OfType<Layoutable>())
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<Layoutable>())
{
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;
}
/// <summary>
/// When overridden in a derived class, positions child elements and determines a
/// size for a System.Windows.FrameworkElement derived class.
/// </summary>
/// <param name="finalSize">
/// The final area within the parent that this element should use to arrange itself
/// and its children.
/// </param>
/// <returns>The actual size used.</returns>
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<Tuple<ILayoutable, Rect>> CalculateLocations(Size finalSize)
private class GraphNode
{
//List of margins for each element between the element and panel (left, top, right, bottom)
List<double[]> arranges = new List<double[]>(Children.Count);
//First pass aligns all sides that aren't constrained by other elements
int arrangedCount = 0;
foreach (var child in Children.OfType<Layoutable>())
public Point Position { get; set; }
public bool Arranged { get; set; }
public Layoutable Element { get; }
public HashSet<GraphNode> 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<GraphNode>();
Element = element;
}
}
private class Graph
{
private readonly Dictionary<AvaloniaObject, GraphNode> _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<AvaloniaObject, GraphNode>();
}
//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<GraphNode> nodes, HashSet<Layoutable> 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<Layoutable>();
}
//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<Layoutable>())
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<ILayoutable>())
{
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<ILayoutable, Rect>(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;
}
}
}

26
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);
}
}
}

Loading…
Cancel
Save