From 8b93c5d839918902a51bd461225ba5c7ab6cbad5 Mon Sep 17 00:00:00 2001 From: SlimeNull Date: Fri, 23 Aug 2024 13:39:44 +0800 Subject: [PATCH] fix: RelativePanel implementation --- .../Controls/Panel/RelativePanel.cs | 2171 ++++++++++++++--- 1 file changed, 1768 insertions(+), 403 deletions(-) diff --git a/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs b/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs index 410341a7f..1a74a213d 100644 --- a/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs +++ b/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs @@ -1,8 +1,15 @@ -//reference doc : https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Controls.RelativePanel +// reference doc : https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.relativepanel +// code from: +// - https://github.com/OrgEleCho/EleCho.WpfSuite/blob/master/EleCho.WpfSuite/Panels/RelativePanel.cs +// - https://github.com/OrgEleCho/EleCho.WpfSuite/blob/master/EleCho.WpfSuite/Panels/RelativePanel.Enums.cs +// - https://github.com/OrgEleCho/EleCho.WpfSuite/blob/master/EleCho.WpfSuite/Panels/RelativePanel.Structures.cs +// - https://github.com/OrgEleCho/EleCho.WpfSuite/blob/master/EleCho.WpfSuite/Panels/RelativePanel.Graph.cs +// - https://github.com/OrgEleCho/EleCho.WpfSuite/blob/master/EleCho.WpfSuite/Panels/RelativePanel.GraphNode.cs using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -12,47 +19,101 @@ namespace HandyControl.Controls; -public class RelativePanel : Panel -{ - private readonly Graph _childGraph; - public RelativePanel() => _childGraph = new Graph(); +/// +/// Defines an area within which you can position and align child objects in relation to each other or the parent panel. +/// +public partial class RelativePanel : Panel +{ + private readonly Graph _graph = new(); #region Panel alignment + /// + /// Identifies the AlignLeftWithPanel attached property + /// public static readonly DependencyProperty AlignLeftWithPanelProperty = DependencyProperty.RegisterAttached( - "AlignLeftWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsRender)); + "AlignLeftWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the AlignTopWithPanel attached property + /// + public static readonly DependencyProperty AlignTopWithPanelProperty = DependencyProperty.RegisterAttached( + "AlignTopWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the AlignRightWithPanel attached property + /// + public static readonly DependencyProperty AlignRightWithPanelProperty = DependencyProperty.RegisterAttached( + "AlignRightWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the AlignBottomWithPanel attached property + /// + public static readonly DependencyProperty AlignBottomWithPanelProperty = DependencyProperty.RegisterAttached( + "AlignBottomWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + /// + /// Set value of AlignLeftWithPanel attached property + /// + /// + /// public static void SetAlignLeftWithPanel(DependencyObject element, bool value) => element.SetValue(AlignLeftWithPanelProperty, ValueBoxes.BooleanBox(value)); + /// + /// Get value of AlignLeftWithPanel attached property + /// + /// + /// public static bool GetAlignLeftWithPanel(DependencyObject element) => (bool) element.GetValue(AlignLeftWithPanelProperty); - public static readonly DependencyProperty AlignTopWithPanelProperty = DependencyProperty.RegisterAttached( - "AlignTopWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignTopWithPanel attached property + /// + /// + /// public static void SetAlignTopWithPanel(DependencyObject element, bool value) => element.SetValue(AlignTopWithPanelProperty, ValueBoxes.BooleanBox(value)); + /// + /// Get value of AlignTopWithPanel attached property + /// + /// + /// public static bool GetAlignTopWithPanel(DependencyObject element) => (bool) element.GetValue(AlignTopWithPanelProperty); - public static readonly DependencyProperty AlignRightWithPanelProperty = DependencyProperty.RegisterAttached( - "AlignRightWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignRightWithPanel attached property + /// + /// + /// public static void SetAlignRightWithPanel(DependencyObject element, bool value) => element.SetValue(AlignRightWithPanelProperty, ValueBoxes.BooleanBox(value)); + /// + /// Get value of AlignRightWithPanel attached property + /// + /// + /// public static bool GetAlignRightWithPanel(DependencyObject element) => (bool) element.GetValue(AlignRightWithPanelProperty); - public static readonly DependencyProperty AlignBottomWithPanelProperty = DependencyProperty.RegisterAttached( - "AlignBottomWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignBottomWithPanel attached property + /// + /// + /// public static void SetAlignBottomWithPanel(DependencyObject element, bool value) => element.SetValue(AlignBottomWithPanelProperty, ValueBoxes.BooleanBox(value)); + /// + /// Get value of AlignBottomWithPanel attached property + /// + /// + /// public static bool GetAlignBottomWithPanel(DependencyObject element) => (bool) element.GetValue(AlignBottomWithPanelProperty); @@ -60,42 +121,94 @@ public static bool GetAlignBottomWithPanel(DependencyObject element) #region Sibling alignment + /// + /// Identifies the AlignLeftWith attached property + /// public static readonly DependencyProperty AlignLeftWithProperty = DependencyProperty.RegisterAttached( - "AlignLeftWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); + "AlignLeftWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the AlignTopWith attached property + /// + public static readonly DependencyProperty AlignTopWithProperty = DependencyProperty.RegisterAttached( + "AlignTopWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + /// + /// Identifies the AlignRightWith attached property + /// + public static readonly DependencyProperty AlignRightWithProperty = DependencyProperty.RegisterAttached( + "AlignRightWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the AlignBottomWith attached property + /// + public static readonly DependencyProperty AlignBottomWithProperty = DependencyProperty.RegisterAttached( + "AlignBottomWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Set value of AlignLeftWith attached property + /// + /// + /// public static void SetAlignLeftWith(DependencyObject element, UIElement value) => element.SetValue(AlignLeftWithProperty, value); + /// + /// Get value of AlignLeftWith attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetAlignLeftWith(DependencyObject element) => (UIElement) element.GetValue(AlignLeftWithProperty); - public static readonly DependencyProperty AlignTopWithProperty = DependencyProperty.RegisterAttached( - "AlignTopWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignTopWith attached property + /// + /// + /// public static void SetAlignTopWith(DependencyObject element, UIElement value) => element.SetValue(AlignTopWithProperty, value); + /// + /// Get value of AlignTopWith attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetAlignTopWith(DependencyObject element) => (UIElement) element.GetValue(AlignTopWithProperty); - public static readonly DependencyProperty AlignRightWithProperty = DependencyProperty.RegisterAttached( - "AlignRightWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignRightWith attached property + /// + /// + /// public static void SetAlignRightWith(DependencyObject element, UIElement value) => element.SetValue(AlignRightWithProperty, value); + /// + /// Get value of AlignRightWith attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetAlignRightWith(DependencyObject element) => (UIElement) element.GetValue(AlignRightWithProperty); - public static readonly DependencyProperty AlignBottomWithProperty = DependencyProperty.RegisterAttached( - "AlignBottomWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignBottomWith attached property + /// + /// + /// public static void SetAlignBottomWith(DependencyObject element, UIElement value) => element.SetValue(AlignBottomWithProperty, value); + /// + /// Get value of AlignBottomWith attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetAlignBottomWith(DependencyObject element) => (UIElement) element.GetValue(AlignBottomWithProperty); @@ -104,42 +217,94 @@ public static UIElement GetAlignBottomWith(DependencyObject element) #region Sibling positional + /// + /// Identifies the LeftOf attached property + /// public static readonly DependencyProperty LeftOfProperty = DependencyProperty.RegisterAttached( - "LeftOf", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); + "LeftOf", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the Above attached property + /// + public static readonly DependencyProperty AboveProperty = DependencyProperty.RegisterAttached( + "Above", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the RightOf attached property + /// + public static readonly DependencyProperty RightOfProperty = DependencyProperty.RegisterAttached( + "RightOf", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the Below attached property + /// + public static readonly DependencyProperty BelowProperty = DependencyProperty.RegisterAttached( + "Below", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + /// + /// Set value of LeftOf attached property + /// + /// + /// public static void SetLeftOf(DependencyObject element, UIElement value) => element.SetValue(LeftOfProperty, value); + /// + /// Get value of LeftOf attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetLeftOf(DependencyObject element) => (UIElement) element.GetValue(LeftOfProperty); - public static readonly DependencyProperty AboveProperty = DependencyProperty.RegisterAttached( - "Above", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of Above attached property + /// + /// + /// public static void SetAbove(DependencyObject element, UIElement value) => element.SetValue(AboveProperty, value); + /// + /// Get value of Above attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetAbove(DependencyObject element) => (UIElement) element.GetValue(AboveProperty); - public static readonly DependencyProperty RightOfProperty = DependencyProperty.RegisterAttached( - "RightOf", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of RightOf attached property + /// + /// + /// public static void SetRightOf(DependencyObject element, UIElement value) => element.SetValue(RightOfProperty, value); + /// + /// Get value of RightOf attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetRightOf(DependencyObject element) => (UIElement) element.GetValue(RightOfProperty); - public static readonly DependencyProperty BelowProperty = DependencyProperty.RegisterAttached( - "Below", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of Below attached property + /// + /// + /// public static void SetBelow(DependencyObject element, UIElement value) => element.SetValue(BelowProperty, value); + /// + /// Get value of Below attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetBelow(DependencyObject element) => (UIElement) element.GetValue(BelowProperty); @@ -148,549 +313,1749 @@ public static UIElement GetBelow(DependencyObject element) #region Center alignment + /// + /// Identifies the AlignHorizontalCenterWithPanel attached property + /// public static readonly DependencyProperty AlignHorizontalCenterWithPanelProperty = DependencyProperty.RegisterAttached( - "AlignHorizontalCenterWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsRender)); + "AlignHorizontalCenterWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the AlignVerticalCenterWithPanel attached property + /// + public static readonly DependencyProperty AlignVerticalCenterWithPanelProperty = DependencyProperty.RegisterAttached( + "AlignVerticalCenterWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsMeasure| FrameworkPropertyMetadataOptions.AffectsArrange)); + /// + /// Identifies the AlignHorizontalCenterWith attached property + /// + public static readonly DependencyProperty AlignHorizontalCenterWithProperty = DependencyProperty.RegisterAttached( + "AlignHorizontalCenterWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Identifies the AlignVerticalCenterWith attached property + /// + public static readonly DependencyProperty AlignVerticalCenterWithProperty = DependencyProperty.RegisterAttached( + "AlignVerticalCenterWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange)); + + /// + /// Set value of AlignHorizontalCenterWithPanel attached property + /// + /// + /// public static void SetAlignHorizontalCenterWithPanel(DependencyObject element, bool value) => element.SetValue(AlignHorizontalCenterWithPanelProperty, ValueBoxes.BooleanBox(value)); + /// + /// Get value of AlignHorizontalCenterWithPanel attached property + /// + /// + /// public static bool GetAlignHorizontalCenterWithPanel(DependencyObject element) => (bool) element.GetValue(AlignHorizontalCenterWithPanelProperty); - public static readonly DependencyProperty AlignVerticalCenterWithPanelProperty = DependencyProperty.RegisterAttached( - "AlignVerticalCenterWithPanel", typeof(bool), typeof(RelativePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignVerticalCenterWithPanel attached property + /// + /// + /// public static void SetAlignVerticalCenterWithPanel(DependencyObject element, bool value) => element.SetValue(AlignVerticalCenterWithPanelProperty, ValueBoxes.BooleanBox(value)); + /// + /// Get value of AlignVerticalCenterWithPanel attached property + /// + /// + /// public static bool GetAlignVerticalCenterWithPanel(DependencyObject element) => (bool) element.GetValue(AlignVerticalCenterWithPanelProperty); - public static readonly DependencyProperty AlignHorizontalCenterWithProperty = DependencyProperty.RegisterAttached( - "AlignHorizontalCenterWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignHorizontalCenterWith attached property + /// + /// + /// public static void SetAlignHorizontalCenterWith(DependencyObject element, UIElement value) => element.SetValue(AlignHorizontalCenterWithProperty, value); + /// + /// Get value of AlignHorizontalCenterWith attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetAlignHorizontalCenterWith(DependencyObject element) => (UIElement) element.GetValue(AlignHorizontalCenterWithProperty); - public static readonly DependencyProperty AlignVerticalCenterWithProperty = DependencyProperty.RegisterAttached( - "AlignVerticalCenterWith", typeof(UIElement), typeof(RelativePanel), new FrameworkPropertyMetadata(default(UIElement), FrameworkPropertyMetadataOptions.AffectsRender)); - + /// + /// Set value of AlignVerticalCenterWith attached property + /// + /// + /// public static void SetAlignVerticalCenterWith(DependencyObject element, UIElement value) => element.SetValue(AlignVerticalCenterWithProperty, value); + /// + /// Get value of AlignVerticalCenterWith attached property + /// + /// + /// [TypeConverter(typeof(NameReferenceConverter))] public static UIElement GetAlignVerticalCenterWith(DependencyObject element) => (UIElement) element.GetValue(AlignVerticalCenterWithProperty); #endregion + + /// protected override Size MeasureOverride(Size availableSize) { - #region Calc DesiredSize - - _childGraph.Clear(); - foreach (UIElement child in InternalChildren) - { - if (child == null) continue; - var node = _childGraph.AddNode(child); - - node.AlignLeftWithNode = _childGraph.AddLink(node, GetAlignLeftWith(child)); - node.AlignTopWithNode = _childGraph.AddLink(node, GetAlignTopWith(child)); - node.AlignRightWithNode = _childGraph.AddLink(node, GetAlignRightWith(child)); - node.AlignBottomWithNode = _childGraph.AddLink(node, GetAlignBottomWith(child)); - - node.LeftOfNode = _childGraph.AddLink(node, GetLeftOf(child)); - node.AboveNode = _childGraph.AddLink(node, GetAbove(child)); - node.RightOfNode = _childGraph.AddLink(node, GetRightOf(child)); - node.BelowNode = _childGraph.AddLink(node, GetBelow(child)); + GenerateGraph(); - node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetAlignHorizontalCenterWith(child)); - node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetAlignVerticalCenterWith(child)); - } - _childGraph.Measure(availableSize); + _graph.MeasureNodes(availableSize); - #endregion + var desiredSizeOfChildren = _graph.CalculateDesiredSize(); - #region Calc AvailableSize + return desiredSizeOfChildren; + } - _childGraph.Reset(false); + /// + protected override Size ArrangeOverride(Size finalSize) + { + _graph.ArrangeNodes(new Rect(0, 0, finalSize.Width, finalSize.Height)); + return finalSize; + } - var calcWidth = Width.IsNaN() && HorizontalAlignment != HorizontalAlignment.Stretch; - var calcHeight = Height.IsNaN() && VerticalAlignment != VerticalAlignment.Stretch; + private void GenerateGraph() + { + _graph.Nodes.Clear(); - var boundingSize = _childGraph.GetBoundingSize(calcWidth, calcHeight); - _childGraph.Reset(); - _childGraph.Measure(boundingSize); - return boundingSize; + foreach (UIElement child in InternalChildren) + { + _graph.Nodes.AddLast(new GraphNode(child)); + } - #endregion + _graph.ResolveConstraints(); } - protected override Size ArrangeOverride(Size arrangeSize) + enum Constraints { - _childGraph.GetNodes().Do(node => node.Arrange(arrangeSize)); - return arrangeSize; + None = 0x00000, + LeftOf = 0x00001, + Above = 0x00002, + RightOf = 0x00004, + Below = 0x00008, + AlignHorizontalCenterWith = 0x00010, + AlignVerticalCenterWith = 0x00020, + AlignLeftWith = 0x00040, + AlignTopWith = 0x00080, + AlignRightWith = 0x00100, + AlignBottomWith = 0x00200, + AlignLeftWithPanel = 0x00400, + AlignTopWithPanel = 0x00800, + AlignRightWithPanel = 0x01000, + AlignBottomWithPanel = 0x02000, + AlignHorizontalCenterWithPanel = 0x04000, + AlignVerticalCenterWithPanel = 0x08000 } - private class GraphNode + enum State { - public bool Measured { get; set; } - - public UIElement Element { get; } - - private bool HorizontalOffsetFlag { get; set; } - - private bool VerticalOffsetFlag { get; set; } - - private Size BoundingSize { get; set; } - - public Size OriginDesiredSize { get; set; } - - public double Left { get; set; } = double.NaN; - - public double Top { get; set; } = double.NaN; - - public double Right { get; set; } = double.NaN; - - public double Bottom { get; set; } = double.NaN; - - public HashSet OutgoingNodes { get; } - - public GraphNode AlignLeftWithNode { get; set; } - - public GraphNode 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; } + Unresolved = 0x00, + Pending = 0x01, + Measured = 0x02, + ArrangedHorizontally = 0x04, + ArrangedVertically = 0x08, + Arranged = 0x0C + } - public GraphNode AlignVerticalCenterWith { get; set; } + private struct UnsafeRect + { + public double X; + public double Y; + public double Width; + public double Height; - public GraphNode(UIElement element) + public UnsafeRect(double x, double y, double width, double height) { - OutgoingNodes = new HashSet(); - Element = element; + X = x; + Y = y; + Width = width; + Height = height; } + } - 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(bool clearPos) + private class Graph + { + private double m_minX; + private double m_maxX; + private double m_minY; + private double m_maxY; + private bool m_isMinCapped; + private bool m_isMaxCapped; + private bool m_knownErrorPending; + private bool m_agErrorCode; + private Size m_availableSizeForNodeResolution; + + public LinkedList Nodes { get; } + + public Graph() { - if (clearPos) - { - Left = double.NaN; - Top = double.NaN; - Right = double.NaN; - Bottom = double.NaN; - } - Measured = false; + m_minX = 0.0f; + m_maxX = 0.0f; + m_minY = 0.0f; + m_maxY = 0.0f; + m_isMinCapped = false; + m_isMaxCapped = false; + m_knownErrorPending = false; + m_agErrorCode = false; + + Nodes = new(); } - public Size GetBoundingSize() + private GraphNode? GetNodeByValue(UIElement? uiElement) { - if (Left < 0 || Top < 0) return default; - if (Measured) return BoundingSize; - - if (!OutgoingNodes.Any()) + if (uiElement is null) { - BoundingSize = Element.DesiredSize; - Measured = true; + return null; } - else + + foreach (var node in Nodes) { - BoundingSize = GetBoundingSize(this, Element.DesiredSize, OutgoingNodes); - Measured = true; + if (node.Element == uiElement) + { + return node; + } } - return BoundingSize; + return null; } - private static Size GetBoundingSize(GraphNode prevNode, Size prevSize, IEnumerable nodes) + // Starting off with the space that is available to the entire panel + // (a.k.a. available size), we will constrain this space little by + // little based on the ArrangeRects of the dependencies that the + // node has. The end result corresponds to the MeasureRect of this node. + // Consider the following example: if an element is to the left of a + // sibling, that means that the space that is available to this element + // in particular is now the available size minus the Width of the + // ArrangeRect associated with this sibling. + private void CalculateMeasureRectHorizontally(GraphNode node, Size availableSize, out double x, out double width) { - foreach (var node in nodes) + bool isHorizontallyCenteredFromLeft = false; + bool isHorizontallyCenteredFromRight = false; + + // The initial values correspond to the entire available space. In + // other words, the edges of the element are aligned to the edges + // of the panel by default. We will now constrain each side of this + // space as necessary. + x = 0.0f; + width = availableSize.Width; + + // If we have infinite available width, then the Width of the + // MeasureRect is also infinite; we do not have to constrain it. + if (availableSize.Width != double.PositiveInfinity) { - if (node.Measured || !node.OutgoingNodes.Any()) + // Constrain the left side of the available space, i.e. + // a) The child has its left edge aligned with the panel (default), + // b) The child has its left edge aligned with the left edge of a sibling, + // or c) The child is positioned to the right of a sibling. + // + // |;; | | + // |;; | | + // |;; |:::::::::::::::| ;;:::::::::::::;; + // |;; |; ;| . ;; ;; + // |;; |; ;| .;;............ ;; ;; + // |;; |; ;| .;;;;:::::::::::: ;; ;; + // |;; |; ;| ':;;:::::::::::: ;; ;; + // |;; |; ;| ': ;; ;; + // |;; |:::::::::::::::| ::::::::::::::::: + // |;; | | + // |;; | | + // AlignLeftWithPanel AlignLeftWith RightOf + // + if (!node.IsAlignLeftWithPanel()) { - if (prevNode.LeftOfNode != null && prevNode.LeftOfNode == node || - prevNode.RightOfNode != null && prevNode.RightOfNode == node) + if (node.IsAlignLeftWith()) + { + GraphNode alignLeftWithNeighbor = node.m_alignLeftWithNode!; + double restrictedHorizontalSpace = alignLeftWithNeighbor.m_arrangeRect.X; + + x = restrictedHorizontalSpace; + width -= restrictedHorizontalSpace; + } + else if (node.IsAlignHorizontalCenterWith()) + { + isHorizontallyCenteredFromLeft = true; + } + else if (node.IsRightOf()) { - prevSize.Width += node.BoundingSize.Width; - if (GetAlignHorizontalCenterWithPanel(node.Element) || node.HorizontalOffsetFlag) - { - prevSize.Width += prevNode.OriginDesiredSize.Width; - prevNode.HorizontalOffsetFlag = true; - } - if (node.VerticalOffsetFlag) - { - prevNode.VerticalOffsetFlag = true; - } + GraphNode rightOfNeighbor = node.m_rightOfNode!; + double restrictedHorizontalSpace = rightOfNeighbor.m_arrangeRect.X + rightOfNeighbor.m_arrangeRect.Width; + + x = restrictedHorizontalSpace; + width -= restrictedHorizontalSpace; } + } + + // Constrain the right side of the available space, i.e. + // a) The child has its right edge aligned with the panel (default), + // b) The child has its right edge aligned with the right edge of a sibling, + // or c) The child is positioned to the left of a sibling. + // + // | | ;;| + // | | ;;| + // ;;:::::::::::::;; |;:::::::::::::;| ;;| + // ;; ;; . |; ;| ;;| + // ;; ;; ............;;. |; ;| ;;| + // ;; ;; ::::::::::::;;;;. |; ;| ;;| + // ;; ;; ::::::::::::;;:' |; ;| ;;| + // ;; ;; :' |; ;| ;;| + // ::::::::::::::::: |:::::::::::::::| ;;| + // | | ;;| + // | | ;;| + // LeftOf AlignRightWith AlignRightWithPanel + // + if (!node.IsAlignRightWithPanel()) + { + if (node.IsAlignRightWith()) + { + GraphNode alignRightWithNeighbor = node.m_alignRightWithNode!; - if (prevNode.AboveNode != null && prevNode.AboveNode == node || - prevNode.BelowNode != null && prevNode.BelowNode == node) + width -= availableSize.Width - (alignRightWithNeighbor.m_arrangeRect.X + alignRightWithNeighbor.m_arrangeRect.Width); + } + else if (node.IsAlignHorizontalCenterWith()) + { + isHorizontallyCenteredFromRight = true; + } + else if (node.IsLeftOf()) { - prevSize.Height += node.BoundingSize.Height; - if (GetAlignVerticalCenterWithPanel(node.Element) || node.VerticalOffsetFlag) - { - prevSize.Height += prevNode.OriginDesiredSize.Height; - prevNode.VerticalOffsetFlag = true; - } - if (node.HorizontalOffsetFlag) - { - prevNode.HorizontalOffsetFlag = true; - } + GraphNode leftOfNeighbor = node.m_leftOfNode!; + + width -= availableSize.Width - leftOfNeighbor.m_arrangeRect.X; } } - else + + if (isHorizontallyCenteredFromLeft && isHorizontallyCenteredFromRight) { - return GetBoundingSize(node, prevSize, node.OutgoingNodes); + GraphNode alignHorizontalCenterWithNeighbor = node.m_alignHorizontalCenterWithNode!; + double centerOfNeighbor = alignHorizontalCenterWithNeighbor.m_arrangeRect.X + (alignHorizontalCenterWithNeighbor.m_arrangeRect.Width / 2.0f); + width = Math.Min(centerOfNeighbor, availableSize.Width - centerOfNeighbor) * 2.0f; + x = centerOfNeighbor - (width / 2.0f); } } - - return prevSize; } - } + private void CalculateMeasureRectVertically(GraphNode node, Size availableSize, out double y, out double height) + { + bool isVerticallyCenteredFromTop = false; + bool isVerticallyCenteredFromBottom = false; + + // The initial values correspond to the entire available space. In + // other words, the edges of the element are aligned to the edges + // of the panel by default. We will now constrain each side of this + // space as necessary. + y = 0.0f; + height = availableSize.Height; + + // If we have infinite available height, then the Height of the + // MeasureRect is also infinite; we do not have to constrain it. + if (availableSize.Height != double.PositiveInfinity) + { + // Constrain the top of the available space, i.e. + // a) The child has its top edge aligned with the panel (default), + // b) The child has its top edge aligned with the top edge of a sibling, + // or c) The child is positioned to the below a sibling. + // + // ================================== AlignTopWithPanel + // :::::::::::::::::::::::::::::::::: + // + // + // + // --------;;=============;;--------- AlignTopWith + // ;; ;; + // ;; ;; + // ;; ;; + // ;; ;; + // ;; ;; + // --------::=============::--------- Below + // . + // .:;:. + // .:;;;;;:. + // ;;;;; + // ;;;;; + // ;;;;; + // ;;;;; + // ;;;;; + // + // ;;:::::::::::::;; + // ;; ;; + // ;; ;; + // ;; ;; + // ;; ;; + // ;; ;; + // ::::::::::::::::: + // + if (!node.IsAlignTopWithPanel()) + { + if (node.IsAlignTopWith()) + { + GraphNode alignTopWithNeighbor = node.m_alignTopWithNode!; + double restrictedVerticalSpace = alignTopWithNeighbor.m_arrangeRect.Y; - private class Graph - { - private readonly Dictionary _nodeDic; + y = restrictedVerticalSpace; + height -= restrictedVerticalSpace; + } + else if (node.IsAlignVerticalCenterWith()) + { + isVerticallyCenteredFromTop = true; + } + else if (node.IsBelow()) + { + GraphNode belowNeighbor = node.m_belowNode!; + double restrictedVerticalSpace = belowNeighbor.m_arrangeRect.Y + belowNeighbor.m_arrangeRect.Height; + + y = restrictedVerticalSpace; + height -= restrictedVerticalSpace; + } + } + + // Constrain the bottom of the available space, i.e. + // a) The child has its bottom edge aligned with the panel (default), + // b) The child has its bottom edge aligned with the bottom edge of a sibling, + // or c) The child is positioned to the above a sibling. + // + // ;;:::::::::::::;; + // ;; ;; + // ;; ;; + // ;; ;; + // ;; ;; + // ;; ;; + // ::::::::::::::::: + // + // ;;;;; + // ;;;;; + // ;;;;; + // ;;;;; + // ;;;;; + // ..;;;;;.. + // ':::::' + // ':` + // + // --------;;=============;;--------- Above + // ;; ;; + // ;; ;; + // ;; ;; + // ;; ;; + // ;; ;; + // --------::=============::--------- AlignBottomWith + // + // + // + // :::::::::::::::::::::::::::::::::: + // ================================== AlignBottomWithPanel + // + if (!node.IsAlignBottomWithPanel()) + { + if (node.IsAlignBottomWith()) + { + GraphNode alignBottomWithNeighbor = node.m_alignBottomWithNode!; - private Size AvailableSize { get; set; } + height -= availableSize.Height - (alignBottomWithNeighbor.m_arrangeRect.Y + alignBottomWithNeighbor.m_arrangeRect.Height); + } + else if (node.IsAlignVerticalCenterWith()) + { + isVerticallyCenteredFromBottom = true; + } + else if (node.IsAbove()) + { + GraphNode aboveNeighbor = node.m_aboveNode!; - public Graph() => _nodeDic = new Dictionary(); + height -= availableSize.Height - aboveNeighbor.m_arrangeRect.Y; + } + } - public IEnumerable GetNodes() => _nodeDic.Values; + if (isVerticallyCenteredFromTop && isVerticallyCenteredFromBottom) + { + GraphNode alignVerticalCenterWithNeighbor = node.m_alignVerticalCenterWithNode!; + double centerOfNeighbor = alignVerticalCenterWithNeighbor.m_arrangeRect.Y + (alignVerticalCenterWithNeighbor.m_arrangeRect.Height / 2.0f); + height = Math.Min(centerOfNeighbor, availableSize.Height - centerOfNeighbor) * 2.0f; + y = centerOfNeighbor - (height / 2.0f); + } + } + } - public void Clear() + // The ArrageRect (a.k.a. layout slot) corresponds to the specific rect + // within the MeasureRect that will be given to an element for it to + // position itself. The exact rect is calculated based on anchoring + // and, unless anchoring dictates otherwise, the size of the + // ArrangeRect is equal to the desired size of the element itself. + // Consider the following example: if the node is right-anchored, the + // right side of the ArrangeRect should overlap with the right side + // of the MeasureRect; this same logic is applied to the other three + // sides of the rect. + private void CalculateArrangeRectHorizontally(GraphNode node, out double x, out double width) { - AvailableSize = new Size(); - _nodeDic.Clear(); - } + UnsafeRect measureRect = node.m_measureRect; + double desiredWidth = Math.Min(measureRect.Width, node.Element.DesiredSize.Width); - public void Reset(bool clearPos = true) => _nodeDic.Values.Do(node => node.Reset(clearPos)); + //Debug.Assert(node.IsMeasured() && (measureRect.Width != double.PositiveInfinity)); - public GraphNode AddLink(GraphNode from, UIElement to) - { - if (to == null) return null; + // The initial values correspond to the left corner, using the + // desired size of element. If no attached properties were set, + // this means that the element will default to the left corner of + // the panel. + x = measureRect.X; + width = desiredWidth; - GraphNode nodeTo; - if (_nodeDic.ContainsKey(to)) + if (node.IsLeftAnchored) { - nodeTo = _nodeDic[to]; + if (node.IsRightAnchored) + { + x = measureRect.X; + width = measureRect.Width; + } + else + { + x = measureRect.X; + width = desiredWidth; + } } - else + else if (node.IsRightAnchored) { - nodeTo = new GraphNode(to); - _nodeDic[to] = nodeTo; + x = measureRect.X + measureRect.Width - desiredWidth; + width = desiredWidth; } - - from.OutgoingNodes.Add(nodeTo); - return nodeTo; - } - - public GraphNode AddNode(UIElement value) - { - if (!_nodeDic.ContainsKey(value)) + else if (node.IsHorizontalCenterAnchored) { - var node = new GraphNode(value); - _nodeDic.Add(value, node); - return node; + x = measureRect.X + (measureRect.Width / 2.0f) - (desiredWidth / 2.0f); + width = desiredWidth; } - - return _nodeDic[value]; - } - - public void Measure(Size availableSize) - { - AvailableSize = EnsureValidSize(availableSize); - Measure(_nodeDic.Values, null); } - - private static Size EnsureValidSize(Size size) + private void CalculateArrangeRectVertically(GraphNode node, out double y, out double height) { - var width = double.IsInfinity(size.Width) ? 0 : size.Width; - var height = double.IsInfinity(size.Height) ? 0 : size.Height; + UnsafeRect measureRect = node.m_measureRect; + double desiredHeight = Math.Min(measureRect.Height, node.Element.DesiredSize.Height); - return new Size(width, height); - } + //Debug.Assert(node.IsMeasured() && (measureRect.Height != double.PositiveInfinity)); - private void Measure(IEnumerable nodes, HashSet set) - { - set ??= new HashSet(); + // The initial values correspond to the top corner, using the + // desired size of element. If no attached properties were set, + // this means that the element will default to the top corner of + // the panel. + y = measureRect.Y; + height = desiredHeight; - foreach (var node in nodes) + if (node.IsTopAnchored) { - /* - * 该节点无任何依赖,所以从这里开始计算元素位置。 - * 因为无任何依赖,所以忽略同级元素 - */ - if (!node.Measured && !node.OutgoingNodes.Any()) - { - MeasureChild(node); - continue; - } - - // 判断依赖元素是否全部排列完毕 - if (node.OutgoingNodes.All(item => item.Measured)) + if (node.IsBottomAnchored) { - MeasureChild(node); - continue; + y = measureRect.Y; + height = measureRect.Height; } - - // 判断是否有循环 - if (!set.Add(node.Element)) throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); - - // 没有循环,且有依赖,则继续往下 - Measure(node.OutgoingNodes, set); - - if (!node.Measured) + else { - MeasureChild(node); + y = measureRect.Y; + height = desiredHeight; } } - } - - private void MeasureChild(GraphNode node) - { - var child = node.Element; - child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - node.OriginDesiredSize = child.DesiredSize; - - var alignLeftWithPanel = GetAlignLeftWithPanel(child); - var alignTopWithPanel = GetAlignTopWithPanel(child); - var alignRightWithPanel = GetAlignRightWithPanel(child); - var alignBottomWithPanel = GetAlignBottomWithPanel(child); - - #region Panel alignment - - if (alignLeftWithPanel) node.Left = 0; - if (alignTopWithPanel) node.Top = 0; - if (alignRightWithPanel) node.Right = 0; - if (alignBottomWithPanel) node.Bottom = 0; - - #endregion - - #region Sibling alignment - - if (node.AlignLeftWithNode != null) + else if (node.IsBottomAnchored) { - node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5; + y = measureRect.Y + measureRect.Height - desiredHeight; + height = desiredHeight; } - - if (node.AlignTopWithNode != null) + else if (node.IsVerticalCenterAnchored) { - node.Top = node.Top.IsNaN() ? node.AlignTopWithNode.Top : node.AlignTopWithNode.Top * 0.5; + y = measureRect.Y + (measureRect.Height / 2.0f) - (desiredHeight / 2.0f); + height = desiredHeight; } - - if (node.AlignRightWithNode != null) + } + private void MarkHorizontalAndVerticalLeaves() + { + foreach (var node in Nodes) { - node.Right = node.Right.IsNaN() - ? node.AlignRightWithNode.Right - : node.AlignRightWithNode.Right * 0.5; + node.m_isHorizontalLeaf = true; + node.m_isVerticalLeaf = true; } - if (node.AlignBottomWithNode != null) + foreach (var node in Nodes) { - node.Bottom = node.Bottom.IsNaN() - ? node.AlignBottomWithNode.Bottom - : node.AlignBottomWithNode.Bottom * 0.5; + node.UnmarkNeighborsAsHorizontalOrVerticalLeaves(); } + } - #endregion + private void AccumulatePositiveDesiredWidth(GraphNode node, double x) + { + double initialX = x; + bool isHorizontallyCenteredFromLeft = false; + bool isHorizontallyCenteredFromRight = false; - #region Measure + Debug.Assert(node.IsMeasured()); - var availableHeight = AvailableSize.Height - node.Top - node.Bottom; - if (availableHeight.IsNaN()) - { - availableHeight = AvailableSize.Height; + // If we are going in the positive direction, move the cursor + // right by the desired width of the node with which we are + // currently working and refresh the maximum positive value. + x += node.Element.DesiredSize.Width; + m_maxX = Math.Max(m_maxX, x); - if (!node.Top.IsNaN() && node.Bottom.IsNaN()) + if (node.IsAlignLeftWithPanel()) + { + if (!m_isMaxCapped) { - availableHeight -= node.Top; + m_maxX = x; + m_isMaxCapped = true; } - else if (node.Top.IsNaN() && !node.Bottom.IsNaN()) + } + else if (node.IsAlignLeftWith()) + { + // If the AlignLeftWithNode and AlignRightWithNode are the + // same element, we can skip the former, since we will move + // through the latter later. + if (node.m_alignLeftWithNode != node.m_alignRightWithNode) { - availableHeight -= node.Bottom; + AccumulateNegativeDesiredWidth(node.m_alignLeftWithNode!, x); } } - - var availableWidth = AvailableSize.Width - node.Left - node.Right; - if (availableWidth.IsNaN()) + else if (node.IsAlignHorizontalCenterWith()) { - availableWidth = AvailableSize.Width; + isHorizontallyCenteredFromLeft = true; + } + else if (node.IsRightOf()) + { + AccumulatePositiveDesiredWidth(node.m_rightOfNode!, x); + } - if (!node.Left.IsNaN() && node.Right.IsNaN()) + if (node.IsAlignRightWithPanel()) + { + if (m_isMinCapped) { - availableWidth -= node.Left; + m_minX = Math.Min(m_minX, initialX); } - else if (node.Left.IsNaN() && !node.Right.IsNaN()) + else { - availableWidth -= node.Right; + m_minX = initialX; + m_isMinCapped = true; } } - - child.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0))); - var childSize = child.DesiredSize; - - #endregion - - #region Sibling positional - - if (node.LeftOfNode != null && node.Left.IsNaN()) + else if (node.IsAlignRightWith()) + { + // If this element's right is aligned to some other + // element's right, now we will be going in the positive + // direction to that other element in order to continue the + // traversal of the dependency chain. But first, since we + // arrived to the node where we currently are by going in + // the positive direction, that means that we have already + // moved the cursor right to calculate the maximum positive + // value, so we will use the initial value of Y. + AccumulatePositiveDesiredWidth(node.m_alignRightWithNode!, initialX); + } + else if (node.IsAlignHorizontalCenterWith()) + { + isHorizontallyCenteredFromRight = true; + } + else if (node.IsLeftOf()) { - node.Left = node.LeftOfNode.Left - childSize.Width; + // If this element is to the left of some other element, + // now we will be going in the negative direction to that + // other element in order to continue the traversal of the + // dependency chain. But first, since we arrived to the + // node where we currently are by going in the positive + // direction, that means that we have already moved the + // cursor right to calculate the maximum positive value, so + // we will use the initial value of X. + AccumulateNegativeDesiredWidth(node.m_leftOfNode!, initialX); } - if (node.AboveNode != null && node.Top.IsNaN()) + if (isHorizontallyCenteredFromLeft && isHorizontallyCenteredFromRight) + { + double centerX = x - (node.Element.DesiredSize.Width / 2.0f); + double edgeX = centerX - (node.m_alignHorizontalCenterWithNode!.Element.DesiredSize.Width / 2.0f); + m_minX = Math.Min(m_minX, edgeX); + AccumulatePositiveDesiredWidth(node.m_alignHorizontalCenterWithNode, edgeX); + } + else if (node.IsHorizontalCenterAnchored) { - node.Top = node.AboveNode.Top - childSize.Height; + // If this node is horizontally anchored to the center, then it + // means that it is the root of this dependency chain based on + // the current definition of precedence for constraints: + // e.g. AlignLeftWithPanel + // > AlignLeftWith + // > RightOf + // > AlignHorizontalCenterWithPanel + // Thus, we can report its width as twice the width of + // either the difference from center to left or the difference + // from center to right, whichever is the greatest. + double centerX = x - (node.Element.DesiredSize.Width / 2.0f); + double upper = m_maxX - centerX; + double lower = centerX - m_minX; + m_maxX = Math.Max(upper, lower) * 2.0f; + m_minX = 0.0f; } + } + private void AccumulateNegativeDesiredWidth(GraphNode node, double x) + { + double initialX = x; + bool isHorizontallyCenteredFromLeft = false; + bool isHorizontallyCenteredFromRight = false; + + Debug.Assert(node.IsMeasured()); - if (node.RightOfNode != null) + // If we are going in the negative direction, move the cursor + // left by the desired width of the node with which we are + // currently working and refresh the minimum negative value. + x -= node.Element.DesiredSize.Width; + m_minX = Math.Min(m_minX, x); + + if (node.IsAlignRightWithPanel()) { - if (node.Right.IsNaN()) + if (!m_isMinCapped) { - node.Right = node.RightOfNode.Right - childSize.Width; + m_minX = x; + m_isMinCapped = true; } - - if (node.Left.IsNaN()) + } + else if (node.IsAlignRightWith()) + { + // If the AlignRightWithNode and AlignLeftWithNode are the + // same element, we can skip the former, since we will move + // through the latter later. + if (node.m_alignRightWithNode != node.m_alignLeftWithNode) { - node.Left = AvailableSize.Width - node.RightOfNode.Right; + AccumulatePositiveDesiredWidth(node.m_alignRightWithNode!, x); } } + else if (node.IsAlignHorizontalCenterWith()) + { + isHorizontallyCenteredFromRight = true; + } + else if (node.IsLeftOf()) + { + AccumulateNegativeDesiredWidth(node.m_leftOfNode!, x); + } - if (node.BelowNode != null) + if (node.IsAlignLeftWithPanel()) { - if (node.Bottom.IsNaN()) + if (m_isMaxCapped) { - node.Bottom = node.BelowNode.Bottom - childSize.Height; + m_maxX = Math.Max(m_maxX, initialX); } - - if (node.Top.IsNaN()) + else { - node.Top = AvailableSize.Height - node.BelowNode.Bottom; + m_maxX = initialX; + m_isMaxCapped = true; } } - - #endregion - - #region Sibling-center alignment - - if (node.AlignHorizontalCenterWith != null) + else if (node.IsAlignLeftWith()) { - 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 node.Left = (node.Left + halfWidthLeft) * 0.5; - - if (node.Right.IsNaN()) node.Right = halfWidthRight; - else node.Right = (node.Right + halfWidthRight) * 0.5; + // If this element's left is aligned to some other element's + // left, now we will be going in the negative direction to + // that other element in order to continue the traversal of + // the dependency chain. But first, since we arrived to the + // node where we currently are by going in the negative + // direction, that means that we have already moved the + // cursor left to calculate the minimum negative value, + // so we will use the initial value of X. + AccumulateNegativeDesiredWidth(node.m_alignLeftWithNode!, initialX); } - - if (node.AlignVerticalCenterWith != null) + else if (node.IsAlignHorizontalCenterWith()) { - 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 node.Top = (node.Top + halfHeightTop) * 0.5; - - if (node.Bottom.IsNaN()) node.Bottom = halfHeightBottom; - else node.Bottom = (node.Bottom + halfHeightBottom) * 0.5; + isHorizontallyCenteredFromLeft = true; } - - #endregion - - #region Panel-center alignment - - if (GetAlignHorizontalCenterWithPanel(child)) + else if (node.IsRightOf()) { - var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5; - - if (node.Left.IsNaN()) node.Left = halfSubWidth; - else node.Left = (node.Left + halfSubWidth) * 0.5; - - if (node.Right.IsNaN()) node.Right = halfSubWidth; - else node.Right = (node.Right + halfSubWidth) * 0.5; + // If this element is to the right of some other element, + // now we will be going in the positive direction to that + // other element in order to continue the traversal of the + // dependency chain. But first, since we arrived to the + // node where we currently are by going in the negative + // direction, that means that we have already moved the + // cursor left to calculate the minimum negative value, so + // we will use the initial value of X. + AccumulatePositiveDesiredWidth(node.m_rightOfNode!, initialX); } - if (GetAlignVerticalCenterWithPanel(child)) + if (isHorizontallyCenteredFromLeft && isHorizontallyCenteredFromRight) { - 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; + double centerX = x + (node.Element.DesiredSize.Width / 2.0f); + double edgeX = centerX + (node.m_alignHorizontalCenterWithNode!.Element.DesiredSize.Width / 2.0f); + m_maxX = Math.Max(m_maxX, edgeX); + AccumulateNegativeDesiredWidth(node.m_alignHorizontalCenterWithNode, edgeX); + } + else if (node.IsHorizontalCenterAnchored) + { + // If this node is horizontally anchored to the center, then it + // means that it is the root of this dependency chain based on + // the current definition of precedence for constraints: + // e.g. AlignLeftWithPanel + // > AlignLeftWith + // > RightOf + // > AlignHorizontalCenterWithPanel + // Thus, we can report its width as twice the width of + // either the difference from center to left or the difference + // from center to right, whichever is the greatest. + double centerX = x + (node.Element.DesiredSize.Width / 2.0f); + double upper = m_maxX - centerX; + double lower = centerX - m_minX; + m_maxX = Math.Max(upper, lower) * 2.0f; + m_minX = 0.0f; } + } + private void AccumulatePositiveDesiredHeight(GraphNode node, double y) + { + double initialY = y; + bool isVerticallyCenteredFromTop = false; + bool isVerticallyCenteredFromBottom = false; + + Debug.Assert(node.IsMeasured()); - #endregion + // If we are going in the positive direction, move the cursor + // up by the desired height of the node with which we are + // currently working and refresh the maximum positive value. + y += node.Element.DesiredSize.Height; + m_maxY = Math.Max(m_maxY, y); - if (node.Left.IsNaN()) + if (node.IsAlignTopWithPanel()) { - if (!node.Right.IsNaN()) - node.Left = AvailableSize.Width - node.Right - childSize.Width; - else + if (!m_isMaxCapped) + { + m_maxY = y; + m_isMaxCapped = true; + } + } + else if (node.IsAlignTopWith()) + { + // If the AlignTopWithNode and AlignBottomWithNode are the + // same element, we can skip the former, since we will move + // through the latter later. + if (node.m_alignTopWithNode != node.m_alignBottomWithNode) { - node.Left = 0; - node.Right = AvailableSize.Width - childSize.Width; + AccumulateNegativeDesiredHeight(node.m_alignTopWithNode!, y); } } - else if (!node.Left.IsNaN() && node.Right.IsNaN()) + else if (node.IsAlignVerticalCenterWith()) + { + isVerticallyCenteredFromTop = true; + } + else if (node.IsBelow()) { - node.Right = AvailableSize.Width - node.Left - childSize.Width; + AccumulatePositiveDesiredHeight(node.m_belowNode!, y); } - if (node.Top.IsNaN()) + if (node.IsAlignBottomWithPanel()) { - if (!node.Bottom.IsNaN()) - node.Top = AvailableSize.Height - node.Bottom - childSize.Height; + if (m_isMinCapped) + { + m_minY = Math.Min(m_minY, initialY); + } else { - node.Top = 0; - node.Bottom = AvailableSize.Height - childSize.Height; + m_minY = initialY; + m_isMinCapped = true; } } - else if (!node.Top.IsNaN() && node.Bottom.IsNaN()) + else if (node.IsAlignBottomWith()) + { + // If this element's bottom is aligned to some other + // element's bottom, now we will be going in the positive + // direction to that other element in order to continue the + // traversal of the dependency chain. But first, since we + // arrived to the node where we currently are by going in + // the positive direction, that means that we have already + // moved the cursor up to calculate the maximum positive + // value, so we will use the initial value of Y. + AccumulatePositiveDesiredHeight(node.m_alignBottomWithNode!, initialY); + } + else if (node.IsAlignVerticalCenterWith()) + { + isVerticallyCenteredFromBottom = true; + } + else if (node.IsAbove()) { - node.Bottom = AvailableSize.Height - node.Top - childSize.Height; + // If this element is above some other element, now we will + // be going in the negative direction to that other element + // in order to continue the traversal of the dependency + // chain. But first, since we arrived to the node where we + // currently are by going in the positive direction, that + // means that we have already moved the cursor up to + // calculate the maximum positive value, so we will use + // the initial value of Y. + AccumulateNegativeDesiredHeight(node.m_aboveNode!, initialY); } - node.Measured = true; + if (isVerticallyCenteredFromTop && isVerticallyCenteredFromBottom) + { + double centerY = y - (node.Element.DesiredSize.Height / 2.0f); + double edgeY = centerY - (node.m_alignVerticalCenterWithNode!.Element.DesiredSize.Height / 2.0f); + m_minY = Math.Min(m_minY, edgeY); + AccumulatePositiveDesiredHeight(node.m_alignVerticalCenterWithNode, edgeY); + } + else if (node.IsVerticalCenterAnchored) + { + // If this node is vertically anchored to the center, then it + // means that it is the root of this dependency chain based on + // the current definition of precedence for constraints: + // e.g. AlignTopWithPanel + // > AlignTopWith + // > Below + // > AlignVerticalCenterWithPanel + // Thus, we can report its height as twice the height of + // either the difference from center to top or the difference + // from center to bottom, whichever is the greatest. + double centerY = y - (node.Element.DesiredSize.Height / 2.0f); + double upper = m_maxY - centerY; + double lower = centerY - m_minY; + m_maxY = Math.Max(upper, lower) * 2.0f; + m_minY = 0.0f; + } } - - public Size GetBoundingSize(bool calcWidth, bool calcHeight) + private void AccumulateNegativeDesiredHeight(GraphNode node, double y) { - var boundingSize = new Size(); + double initialY = y; + bool isVerticallyCenteredFromTop = false; + bool isVerticallyCenteredFromBottom = false; - foreach (var node in _nodeDic.Values) - { - var size = node.GetBoundingSize(); - boundingSize.Width = Math.Max(boundingSize.Width, size.Width); - boundingSize.Height = Math.Max(boundingSize.Height, size.Height); - } + Debug.Assert(node.IsMeasured()); + + // If we are going in the negative direction, move the cursor + // down by the desired height of the node with which we are + // currently working and refresh the minimum negative value. + y -= node.Element.DesiredSize.Height; + m_minY = Math.Min(m_minY, y); + + if (node.IsAlignBottomWithPanel()) + { + if (!m_isMinCapped) + { + m_minY = y; + m_isMinCapped = true; + } + } + else if (node.IsAlignBottomWith()) + { + // If the AlignBottomWithNode and AlignTopWithNode are the + // same element, we can skip the former, since we will move + // through the latter later. + if (node.m_alignBottomWithNode != node.m_alignTopWithNode) + { + AccumulatePositiveDesiredHeight(node.m_alignBottomWithNode!, y); + } + } + else if (node.IsAlignVerticalCenterWith()) + { + isVerticallyCenteredFromBottom = true; + } + else if (node.IsAbove()) + { + AccumulateNegativeDesiredHeight(node.m_aboveNode!, y); + } + + if (node.IsAlignTopWithPanel()) + { + if (m_isMaxCapped) + { + m_maxY = Math.Max(m_maxY, initialY); + } + else + { + m_maxY = initialY; + m_isMaxCapped = true; + } + } + else if (node.IsAlignTopWith()) + { + // If this element's top is aligned to some other element's + // top, now we will be going in the negative direction to + // that other element in order to continue the traversal of + // the dependency chain. But first, since we arrived to the + // node where we currently are by going in the negative + // direction, that means that we have already moved the + // cursor down to calculate the minimum negative value, + // so we will use the initial value of Y. + AccumulateNegativeDesiredHeight(node.m_alignTopWithNode!, initialY); + } + else if (node.IsAlignVerticalCenterWith()) + { + isVerticallyCenteredFromTop = true; + } + else if (node.IsBelow()) + { + // If this element is below some other element, now we'll + // be going in the positive direction to that other element + // in order to continue the traversal of the dependency + // chain. But first, since we arrived to the node where we + // currently are by going in the negative direction, that + // means that we have already moved the cursor down to + // calculate the minimum negative value, so we will use + // the initial value of Y. + AccumulatePositiveDesiredHeight(node.m_belowNode!, initialY); + } + + if (isVerticallyCenteredFromTop && isVerticallyCenteredFromBottom) + { + double centerY = y + (node.Element.DesiredSize.Height / 2.0f); + double edgeY = centerY + (node.m_alignVerticalCenterWithNode!.Element.DesiredSize.Height / 2.0f); + m_maxY = Math.Max(m_maxY, edgeY); + AccumulateNegativeDesiredHeight(node.m_alignVerticalCenterWithNode, edgeY); + } + else if (node.IsVerticalCenterAnchored) + { + // If this node is vertically anchored to the center, then it + // means that it is the root of this dependency chain based on + // the current definition of precedence for constraints: + // e.g. AlignTopWithPanel + // > AlignTopWith + // > Below + // > AlignVerticalCenterWithPanel + // Thus, we can report its height as twice the height of + // either the difference from center to top or the difference + // from center to bottom, whichever is the greatest. + double centerY = y + (node.Element.DesiredSize.Height / 2.0f); + double upper = m_maxY - centerY; + double lower = centerY - m_minY; + m_maxY = Math.Max(upper, lower) * 2.0f; + m_minY = 0.0f; + } + } + + // Calculates the MeasureRect of a node and then calls Measure on the + // corresponding element by passing the Width and Height of this rect. + // Given that the calculation of the MeasureRect requires the + // ArrangeRects of the dependencies, we call this method recursively on + // said dependencies first and calculate both rects as we go. In other + // words, this method is figuratively a combination of a measure pass + // plus a pseudo-arrange pass. + private void MeasureNode(GraphNode? node, Size availableSize) + { + if (node is null) + { + return; + } + + if (node.IsPending()) + { + // If the node is already in the process of being resolved + // but we tried to resolve it again, that means we are in the + // middle of circular dependency and we must throw an + // InvalidOperationException. We will fail fast here and let + // the CRelativePanel handle the rest. + m_knownErrorPending = true; + //m_agErrorCode = AG_E_RELATIVEPANEL_CIRCULAR_DEP; + //E_FAIL; + + throw new InvalidOperationException("Circular dependency detected"); + } + else if (node.IsUnresolved()) + { + Size constrainedAvailableSize = new(); + + // We must resolve the dependencies of this node first. + // In the meantime, we will mark the state as pending. + node.SetPending(true); + + MeasureNode(node.m_leftOfNode, availableSize); + MeasureNode(node.m_aboveNode, availableSize); + MeasureNode(node.m_rightOfNode, availableSize); + MeasureNode(node.m_belowNode, availableSize); + MeasureNode(node.m_alignLeftWithNode, availableSize); + MeasureNode(node.m_alignTopWithNode, availableSize); + MeasureNode(node.m_alignRightWithNode, availableSize); + MeasureNode(node.m_alignBottomWithNode, availableSize); + MeasureNode(node.m_alignHorizontalCenterWithNode, availableSize); + MeasureNode(node.m_alignVerticalCenterWithNode, availableSize); + + node.SetPending(false); + + CalculateMeasureRectHorizontally(node, availableSize, out node.m_measureRect.X, out node.m_measureRect.Width); + CalculateMeasureRectVertically(node, availableSize, out node.m_measureRect.Y, out node.m_measureRect.Height); + + constrainedAvailableSize.Width = Math.Max(node.m_measureRect.Width, 0.0f); + constrainedAvailableSize.Height = Math.Max(node.m_measureRect.Height, 0.0f); + node.Element.Measure(constrainedAvailableSize); + node.SetMeasured(true); + + // (Pseudo-) Arranging against infinity does not make sense, so + // we will skip the calculations of the ArrangeRects if + // necessary. During the true arrange pass, we will be given a + // non-infinite final size; we will do the necessary + // calculations until then. + if (availableSize.Width != double.PositiveInfinity) + { + CalculateArrangeRectHorizontally(node, out node.m_arrangeRect.X, out node.m_arrangeRect.Width); + node.SetArrangedHorizontally(true); + } + + if (availableSize.Height != double.PositiveInfinity) + { + CalculateArrangeRectVertically(node, out node.m_arrangeRect.Y, out node.m_arrangeRect.Height); + node.SetArrangedVertically(true); + } + } + } + + // Calculates the X and Width properties of the ArrangeRect of a node + // as well as the X and Width properties of the MeasureRect (which is + // necessary in order to calculate the former correctly). Given that + // the calculation of the MeasureRect requires the ArrangeRects of the + // dependencies, we call this method recursively on said dependencies + // first. + private void ArrangeNodeHorizontally(GraphNode? node, Size finalSize) + { + if (node is null) + { + return; + } + + if (!node.IsArrangedHorizontally()) + { + // We must resolve dependencies first. + ArrangeNodeHorizontally(node.m_leftOfNode, finalSize); + ArrangeNodeHorizontally(node.m_aboveNode, finalSize); + ArrangeNodeHorizontally(node.m_rightOfNode, finalSize); + ArrangeNodeHorizontally(node.m_belowNode, finalSize); + ArrangeNodeHorizontally(node.m_alignLeftWithNode, finalSize); + ArrangeNodeHorizontally(node.m_alignTopWithNode, finalSize); + ArrangeNodeHorizontally(node.m_alignRightWithNode, finalSize); + ArrangeNodeHorizontally(node.m_alignBottomWithNode, finalSize); + ArrangeNodeHorizontally(node.m_alignHorizontalCenterWithNode, finalSize); + ArrangeNodeHorizontally(node.m_alignVerticalCenterWithNode, finalSize); + + CalculateMeasureRectHorizontally(node, finalSize, out node.m_measureRect.X, out node.m_measureRect.Width); + CalculateArrangeRectHorizontally(node, out node.m_arrangeRect.X, out node.m_arrangeRect.Width); + + node.SetArrangedHorizontally(true); + } + } + + // Calculates the Y and Height properties of the ArrangeRect of a node + // as well as the Y and Height properties of the MeasureRect (which is + // necessary in order to calculate the former correctly).Given that + // the calculation of the MeasureRect requires the ArrangeRects of the + // dependencies, we call this method recursively on said dependencies + // first. + private void ArrangeNodeVertically(GraphNode? node, Size finalSize) + { + if (node is null) + { + return; + } + + if (!node.IsArrangedVertically()) + { + // We must resolve dependencies first. + ArrangeNodeVertically(node.m_leftOfNode, finalSize); + ArrangeNodeVertically(node.m_aboveNode, finalSize); + ArrangeNodeVertically(node.m_rightOfNode, finalSize); + ArrangeNodeVertically(node.m_belowNode, finalSize); + ArrangeNodeVertically(node.m_alignLeftWithNode, finalSize); + ArrangeNodeVertically(node.m_alignTopWithNode, finalSize); + ArrangeNodeVertically(node.m_alignRightWithNode, finalSize); + ArrangeNodeVertically(node.m_alignBottomWithNode, finalSize); + ArrangeNodeVertically(node.m_alignHorizontalCenterWithNode, finalSize); + ArrangeNodeVertically(node.m_alignVerticalCenterWithNode, finalSize); + + CalculateMeasureRectVertically(node, finalSize, out node.m_measureRect.Y, out node.m_measureRect.Height); + CalculateArrangeRectVertically(node, out node.m_arrangeRect.Y, out node.m_arrangeRect.Height); + + node.SetArrangedVertically(true); + } + } + + private void ResolveConstraint(GraphNode node) + { + if (RelativePanel.GetLeftOf(node.Element) is UIElement leftOf) + { + var leftOfNode = GetNodeByValue(leftOf); + node.SetLeftOfConstraint(leftOfNode); + } + + if (RelativePanel.GetAbove(node.Element) is UIElement above) + { + var aboveNode = GetNodeByValue(above); + node.SetAboveConstraint(aboveNode); + } + + if (RelativePanel.GetRightOf(node.Element) is UIElement rightOf) + { + var rightOfNode = GetNodeByValue(rightOf); + node.SetRightOfConstraint(rightOfNode); + } + + if (RelativePanel.GetBelow(node.Element) is UIElement below) + { + var belowNode = GetNodeByValue(below); + node.SetBelowConstraint(belowNode); + } + + if (RelativePanel.GetAlignHorizontalCenterWith(node.Element) is UIElement alignHorizontalCenterWith) + { + var alignHorizontalCenterWithNode = GetNodeByValue(alignHorizontalCenterWith); + node.SetAlignHorizontalCenterWithConstraint(alignHorizontalCenterWithNode); + } + + if (RelativePanel.GetAlignVerticalCenterWith(node.Element) is UIElement alignVerticalCenterWith) + { + var alignVerticalCenterWithNode = GetNodeByValue(alignVerticalCenterWith); + node.SetAlignVerticalCenterWithConstraint(alignVerticalCenterWithNode); + } + + if (RelativePanel.GetAlignLeftWith(node.Element) is UIElement alignLeftWith) + { + var alignLeftWithNode = GetNodeByValue(alignLeftWith); + node.SetAlignLeftWithConstraint(alignLeftWithNode); + } + + if (RelativePanel.GetAlignTopWith(node.Element) is UIElement alignTopWith) + { + var alignTopWithNode = GetNodeByValue(alignTopWith); + node.SetAlignTopWithConstraint(alignTopWithNode); + } + + if (RelativePanel.GetAlignRightWith(node.Element) is UIElement alignRightWith) + { + var alignRightWithNode = GetNodeByValue(alignRightWith); + node.SetAlignRightWithConstraint(alignRightWithNode); + } + + if (RelativePanel.GetAlignBottomWith(node.Element) is UIElement alignBottomWith) + { + var alignBottomWithNode = GetNodeByValue(alignBottomWith); + node.SetAlignBottomWithConstraint(alignBottomWithNode); + } + + node.SetAlignLeftWithPanelConstraint(GetAlignLeftWithPanel(node.Element)); + node.SetAlignTopWithPanelConstraint(GetAlignTopWithPanel(node.Element)); + node.SetAlignRightWithPanelConstraint(GetAlignRightWithPanel(node.Element)); + node.SetAlignBottomWithPanelConstraint(GetAlignBottomWithPanel(node.Element)); + node.SetAlignHorizontalCenterWithPanelConstraint(GetAlignHorizontalCenterWithPanel(node.Element)); + node.SetAlignVerticalCenterWithPanelConstraint(GetAlignVerticalCenterWithPanel(node.Element)); + } + + public void ResolveConstraints() + { + foreach (var node in Nodes) + { + ResolveConstraint(node); + } + } + + public void MeasureNodes(Size availableSize) + { + foreach (var node in Nodes) + { + MeasureNode(node, availableSize); + } + + m_availableSizeForNodeResolution = availableSize; + } + + public void ArrangeNodes(Rect finalRect) + { + Size finalSize = new Size(finalRect.Width, finalRect.Height); + + // If the final size is the same as the available size that we used + // to measure the nodes, this means that the pseudo-arrange pass + // that we did during the measure pass is, in fact, valid and the + // ArrangeRects that were calculated for each node are correct. In + // other words, we can just go ahead and call arrange on each + // element. However, if the width and/or height of the final size + // differs (e.g. when the element's HorizontalAlignment and/or + // VerticalAlignment is something other than Stretch and thus the final + // size corresponds to the desired size of the panel), we must first + // recalculate the horizontal and/or vertical values of the ArrangeRects, + // respectively. + if (m_availableSizeForNodeResolution.Width != finalSize.Width) + { + foreach (GraphNode node in Nodes) + { + node.SetArrangedHorizontally(false); + } + + foreach (GraphNode node in Nodes) + { + ArrangeNodeHorizontally(node, finalSize); + } + } + + if (m_availableSizeForNodeResolution.Height != finalSize.Height) + { + foreach (GraphNode node in Nodes) + { + node.SetArrangedVertically(false); + } + + foreach (GraphNode node in Nodes) + { + ArrangeNodeVertically(node, finalSize); + } + } + + m_availableSizeForNodeResolution = finalSize; + + foreach (GraphNode node in Nodes) + { + Debug.Assert(node.IsArranged()); + + Rect layoutSlot = new Rect( + Math.Max(node.m_arrangeRect.X + finalRect.X, 0.0f), + Math.Max(node.m_arrangeRect.Y + finalRect.Y, 0.0f), + Math.Max(node.m_arrangeRect.Width, 0.0f), + Math.Max(node.m_arrangeRect.Height, 0.0f)); + + node.Element.Arrange(layoutSlot); + } - boundingSize.Width = calcWidth ? boundingSize.Width : AvailableSize.Width; - boundingSize.Height = calcHeight ? boundingSize.Height : AvailableSize.Height; - return boundingSize; } + + public Size CalculateDesiredSize() + { + Size maxDesiredSize = new(0.0, 0.0); + + MarkHorizontalAndVerticalLeaves(); + + foreach (var node in Nodes) + { + if (node.m_isHorizontalLeaf) + { + m_minX = 0.0f; + m_maxX = 0.0f; + m_isMinCapped = false; + m_isMaxCapped = false; + + AccumulatePositiveDesiredWidth(node, 0.0f); + maxDesiredSize.Width = Math.Max(maxDesiredSize.Width, m_maxX - m_minX); + } + + if (node.m_isVerticalLeaf) + { + m_minY = 0.0f; + m_maxY = 0.0f; + m_isMinCapped = false; + m_isMaxCapped = false; + + AccumulatePositiveDesiredHeight(node, 0.0f); + maxDesiredSize.Height = Math.Max(maxDesiredSize.Height, m_maxY - m_minY); + } + } + + return maxDesiredSize; + } + } + + private class GraphNode + { + private readonly UIElement m_element; + private State m_state; + private Constraints m_constraints; + internal bool m_isHorizontalLeaf; + internal bool m_isVerticalLeaf; + internal GraphNode? m_leftOfNode; + internal GraphNode? m_aboveNode; + internal GraphNode? m_rightOfNode; + internal GraphNode? m_belowNode; + internal GraphNode? m_alignHorizontalCenterWithNode; + internal GraphNode? m_alignVerticalCenterWithNode; + internal GraphNode? m_alignLeftWithNode; + internal GraphNode? m_alignTopWithNode; + internal GraphNode? m_alignRightWithNode; + internal GraphNode? m_alignBottomWithNode; + internal UnsafeRect m_measureRect; + internal UnsafeRect m_arrangeRect; + + public UIElement Element => m_element; + + public GraphNode(UIElement element) + { + m_element = element; + m_state = State.Unresolved; + m_isHorizontalLeaf = true; + m_isVerticalLeaf = true; + m_constraints = Constraints.None; + m_leftOfNode = default(GraphNode); + m_aboveNode = default(GraphNode); + m_rightOfNode = default(GraphNode); + m_belowNode = default(GraphNode); + m_alignHorizontalCenterWithNode = default(GraphNode); + m_alignVerticalCenterWithNode = default(GraphNode); + m_alignLeftWithNode = default(GraphNode); + m_alignTopWithNode = default(GraphNode); + m_alignRightWithNode = default(GraphNode); + m_alignBottomWithNode = default(GraphNode); + } + + + // The node is said to be anchored when its ArrangeRect is expected to + // align with its MeasureRect on one or more sides. For example, if the + // node is right-anchored, the right side of the ArrangeRect should overlap + // with the right side of the MeasureRect. Anchoring is determined by + // specific combinations of dependencies. + public bool IsLeftAnchored + => (IsAlignLeftWithPanel() || IsAlignLeftWith() || (IsRightOf() && !IsAlignHorizontalCenterWith())); + + public bool IsTopAnchored + => (IsAlignTopWithPanel() || IsAlignTopWith() || (IsBelow() && !IsAlignVerticalCenterWith())); + + public bool IsRightAnchored + => (IsAlignRightWithPanel() || IsAlignRightWith() || (IsLeftOf() && !IsAlignHorizontalCenterWith())); + + public bool IsBottomAnchored + => (IsAlignBottomWithPanel() || IsAlignBottomWith() || (IsAbove() && !IsAlignVerticalCenterWith())); + + public bool IsHorizontalCenterAnchored + => ((IsAlignHorizontalCenterWithPanel() && !IsAlignLeftWithPanel() && !IsAlignRightWithPanel() && !IsAlignLeftWith() && !IsAlignRightWith() && !IsLeftOf() && !IsRightOf()) + || (IsAlignHorizontalCenterWith() && !IsAlignLeftWithPanel() && !IsAlignRightWithPanel() && !IsAlignLeftWith() && !IsAlignRightWith())); + + public bool IsVerticalCenterAnchored + => ((IsAlignVerticalCenterWithPanel() && !IsAlignTopWithPanel() && !IsAlignBottomWithPanel() && !IsAlignTopWith() && !IsAlignBottomWith() && !IsAbove() && !IsBelow()) + || (IsAlignVerticalCenterWith() && !IsAlignTopWithPanel() && !IsAlignBottomWithPanel() && !IsAlignTopWith() && !IsAlignBottomWith())); + + // RPState flag checks. + public bool IsUnresolved() { return m_state == State.Unresolved; } + public bool IsPending() { return (m_state & State.Pending) == State.Pending; } + public bool IsMeasured() { return (m_state & State.Measured) == State.Measured; } + public bool IsArrangedHorizontally() { return (m_state & State.ArrangedHorizontally) == State.ArrangedHorizontally; } + public bool IsArrangedVertically() { return (m_state & State.ArrangedVertically) == State.ArrangedVertically; } + public bool IsArranged() { return (m_state & State.Arranged) == State.Arranged; } + + public void SetPending(bool value) + { + if (value) + { + m_state |= State.Pending; + } + else + { + m_state &= ~State.Pending; + } + } + public void SetMeasured(bool value) + { + if (value) + { + m_state |= State.Measured; + } + else + { + m_state &= ~State.Measured; + } + } + public void SetArrangedHorizontally(bool value) + { + if (value) + { + m_state |= State.ArrangedHorizontally; + } + else + { + m_state &= ~State.ArrangedHorizontally; + } + } + public void SetArrangedVertically(bool value) + { + if (value) + { + m_state |= State.ArrangedVertically; + } + else + { + m_state &= ~State.ArrangedVertically; + } + } + + // RPEdge flag checks. + + public bool IsLeftOf() { return (m_constraints & Constraints.LeftOf) == Constraints.LeftOf; } + public bool IsAbove() { return (m_constraints & Constraints.Above) == Constraints.Above; } + public bool IsRightOf() { return (m_constraints & Constraints.RightOf) == Constraints.RightOf; } + public bool IsBelow() { return (m_constraints & Constraints.Below) == Constraints.Below; } + public bool IsAlignHorizontalCenterWith() { return (m_constraints & Constraints.AlignHorizontalCenterWith) == Constraints.AlignHorizontalCenterWith; } + public bool IsAlignVerticalCenterWith() { return (m_constraints & Constraints.AlignVerticalCenterWith) == Constraints.AlignVerticalCenterWith; } + public bool IsAlignLeftWith() { return (m_constraints & Constraints.AlignLeftWith) == Constraints.AlignLeftWith; } + public bool IsAlignTopWith() { return (m_constraints & Constraints.AlignTopWith) == Constraints.AlignTopWith; } + public bool IsAlignRightWith() { return (m_constraints & Constraints.AlignRightWith) == Constraints.AlignRightWith; } + public bool IsAlignBottomWith() { return (m_constraints & Constraints.AlignBottomWith) == Constraints.AlignBottomWith; } + public bool IsAlignLeftWithPanel() { return (m_constraints & Constraints.AlignLeftWithPanel) == Constraints.AlignLeftWithPanel; } + public bool IsAlignTopWithPanel() { return (m_constraints & Constraints.AlignTopWithPanel) == Constraints.AlignTopWithPanel; } + public bool IsAlignRightWithPanel() { return (m_constraints & Constraints.AlignRightWithPanel) == Constraints.AlignRightWithPanel; } + public bool IsAlignBottomWithPanel() { return (m_constraints & Constraints.AlignBottomWithPanel) == Constraints.AlignBottomWithPanel; } + public bool IsAlignHorizontalCenterWithPanel() { return (m_constraints & Constraints.AlignHorizontalCenterWithPanel) == Constraints.AlignHorizontalCenterWithPanel; } + public bool IsAlignVerticalCenterWithPanel() { return (m_constraints & Constraints.AlignVerticalCenterWithPanel) == Constraints.AlignVerticalCenterWithPanel; } + + public void SetLeftOfConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_leftOfNode = neighbor; + m_constraints |= Constraints.LeftOf; + } + else + { + m_leftOfNode = null; + m_constraints &= ~Constraints.LeftOf; + } + } + public void SetAboveConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_aboveNode = neighbor; + m_constraints |= Constraints.Above; + } + else + { + m_aboveNode = null; + m_constraints &= ~Constraints.Above; + } + } + public void SetRightOfConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_rightOfNode = neighbor; + m_constraints |= Constraints.RightOf; + } + else + { + m_rightOfNode = null; + m_constraints &= ~Constraints.RightOf; + } + } + public void SetBelowConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_belowNode = neighbor; + m_constraints |= Constraints.Below; + } + else + { + m_belowNode = null; + m_constraints &= ~Constraints.Below; + } + } + public void SetAlignHorizontalCenterWithConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_alignHorizontalCenterWithNode = neighbor; + m_constraints |= Constraints.AlignHorizontalCenterWith; + } + else + { + m_alignHorizontalCenterWithNode = null; + m_constraints &= ~Constraints.AlignHorizontalCenterWith; + } + } + public void SetAlignVerticalCenterWithConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_alignVerticalCenterWithNode = neighbor; + m_constraints |= Constraints.AlignVerticalCenterWith; + } + else + { + m_alignVerticalCenterWithNode = null; + m_constraints &= ~Constraints.AlignVerticalCenterWith; + } + } + public void SetAlignLeftWithConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_alignLeftWithNode = neighbor; + m_constraints |= Constraints.AlignLeftWith; + } + else + { + m_alignLeftWithNode = null; + m_constraints &= ~Constraints.AlignLeftWith; + } + } + public void SetAlignTopWithConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_alignTopWithNode = neighbor; + m_constraints |= Constraints.AlignTopWith; + } + else + { + m_alignTopWithNode = null; + m_constraints &= ~Constraints.AlignTopWith; + } + } + public void SetAlignRightWithConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_alignRightWithNode = neighbor; + m_constraints |= Constraints.AlignRightWith; + } + else + { + m_alignRightWithNode = null; + m_constraints &= ~Constraints.AlignRightWith; + } + } + public void SetAlignBottomWithConstraint(GraphNode? neighbor) + { + if (neighbor is not null) + { + m_alignBottomWithNode = neighbor; + m_constraints |= Constraints.AlignBottomWith; + } + else + { + m_alignBottomWithNode = null; + m_constraints &= ~Constraints.AlignBottomWith; + } + } + public void SetAlignLeftWithPanelConstraint(bool value) + { + if (value) + { + m_constraints |= Constraints.AlignLeftWithPanel; + } + else + { + m_constraints &= ~Constraints.AlignLeftWithPanel; + } + } + public void SetAlignTopWithPanelConstraint(bool value) + { + if (value) + { + m_constraints |= Constraints.AlignTopWithPanel; + } + else + { + m_constraints &= ~Constraints.AlignTopWithPanel; + } + } + public void SetAlignRightWithPanelConstraint(bool value) + { + if (value) + { + m_constraints |= Constraints.AlignRightWithPanel; + } + else + { + m_constraints &= ~Constraints.AlignRightWithPanel; + } + } + public void SetAlignBottomWithPanelConstraint(bool value) + { + if (value) + { + m_constraints |= Constraints.AlignBottomWithPanel; + } + else + { + m_constraints &= ~Constraints.AlignBottomWithPanel; + } + } + public void SetAlignHorizontalCenterWithPanelConstraint(bool value) + { + if (value) + { + m_constraints |= Constraints.AlignHorizontalCenterWithPanel; + } + else + { + m_constraints &= ~Constraints.AlignHorizontalCenterWithPanel; + } + } + public void SetAlignVerticalCenterWithPanelConstraint(bool value) + { + if (value) + { + m_constraints |= Constraints.AlignVerticalCenterWithPanel; + } + else + { + m_constraints &= ~Constraints.AlignVerticalCenterWithPanel; + } + } + + public void UnmarkNeighborsAsHorizontalOrVerticalLeaves() + { + bool isHorizontallyCenteredFromLeft = false; + bool isHorizontallyCenteredFromRight = false; + bool isVerticallyCenteredFromTop = false; + bool isVerticallyCenteredFromBottom = false; + + if (!IsAlignLeftWithPanel()) + { + if (IsAlignLeftWith()) + { + m_alignLeftWithNode!.m_isHorizontalLeaf = false; + } + else if (IsAlignHorizontalCenterWith()) + { + isHorizontallyCenteredFromLeft = true; + } + else if (IsRightOf()) + { + m_rightOfNode!.m_isHorizontalLeaf = false; + } + } + + if (!IsAlignTopWithPanel()) + { + if (IsAlignTopWith()) + { + m_alignTopWithNode!.m_isVerticalLeaf = false; + } + else if (IsAlignVerticalCenterWith()) + { + isVerticallyCenteredFromTop = true; + } + else if (IsBelow()) + { + m_belowNode!.m_isVerticalLeaf = false; + } + } + + if (!IsAlignRightWithPanel()) + { + if (IsAlignRightWith()) + { + m_alignRightWithNode!.m_isHorizontalLeaf = false; + } + else if (IsAlignHorizontalCenterWith()) + { + isHorizontallyCenteredFromRight = true; + } + else if (IsLeftOf()) + { + m_leftOfNode!.m_isHorizontalLeaf = false; + } + } + + if (!IsAlignBottomWithPanel()) + { + if (IsAlignBottomWith()) + { + m_alignBottomWithNode!.m_isVerticalLeaf = false; + } + else if (IsAlignVerticalCenterWith()) + { + isVerticallyCenteredFromBottom = true; + } + else if (IsAbove()) + { + m_aboveNode!.m_isVerticalLeaf = false; + } + } + + if (isHorizontallyCenteredFromLeft && isHorizontallyCenteredFromRight) + { + m_alignHorizontalCenterWithNode!.m_isHorizontalLeaf = false; + } + + if (isVerticallyCenteredFromTop && isVerticallyCenteredFromBottom) + { + m_alignVerticalCenterWithNode!.m_isVerticalLeaf = false; + } + } + } }