3 changed files with 283 additions and 425 deletions
@ -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; |
|||
} |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue