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