@ -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 arrang eSize)
protected override Size MeasureOverride ( Size availabl eSize)
{
_ 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 ( false ) ;
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,106 @@ 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 ( bool clearPos )
{
if ( clearPos )
{
Left = double . NaN ;
Top = double . NaN ;
Right = double . NaN ;
Bottom = double . NaN ;
}
Measured = false ;
}
public Size GetBoundingSize ( )
{
if ( Left < 0 | | Top < 0 ) return default ;
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 ( ) = > _ nodeDic = new Dictionary < AvaloniaObject , GraphNode > ( ) ;
public IEnumerable < GraphNode > GetNodes ( ) = > _ nodeDic . Values ;
public Graph ( )
public void Clear ( )
{
_ nodeDic = new Dictionary < AvaloniaObject , GraphNode > ( ) ;
AvailableSize = new Size ( ) ;
_ nodeDic . Clear ( ) ;
}
public void Reset ( bool clearPos = true ) = > _ nodeDic . Values . Do ( node = > node . Reset ( clearPos ) ) ;
public GraphNode ? AddLink ( GraphNode from , Layoutable ? to )
{
if ( to = = null )
@ -177,177 +248,289 @@ namespace Avalonia.Controls
return _ nodeDic [ value ] ;
}
public void Reset ( Size arrang eSize)
public void Measure ( Size availabl eSize)
{
_ arrangeSize = arrang eSize;
_ nodeDic . Clear ( ) ;
AvailableSize = availabl eSize;
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 . Arrang ed & & node . OutgoingNodes . Count = = 0 )
if ( ! node . Measur ed & & ! node . OutgoingNodes . Any ( ) )
{
Arrang eChild( node , tru e ) ;
Measur eChild( node ) ;
continue ;
}
if ( node . OutgoingNodes . All ( item = > item . Arrang ed) )
if ( node . OutgoingNodes . All ( item = > item . Measur ed) )
{
Arrang eChild( node ) ;
Measur eChild( node ) ;
continue ;
}
if ( ! set . Add ( node . Element ) )
return true ;
throw new Exception ( "RelativePanel error: Circular dependency detected. Layout could not complete." ) ;
Measure ( node . OutgoingNodes , set ) ;
return CheckCyclic ( node . OutgoingNodes , set ) ;
if ( ! node . Measured )
{
MeasureChild ( node ) ;
}
}
return false ;
}
private void Arrang eChild( GraphNode node , bool ignoneSibling = fals e )
private void Measur eChild( 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 ;
}
}
if ( node . AlignLeftWithNode ! = null )
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 . 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 ( alignLeftWithPane l)
if ( node . AlignHorizontalCenterWith ! = nul l)
{
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 ! = nul l)
{
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 ) ) ;
}
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 ) ;
}
child . Arrange ( new Rect ( childPos . X , childPos . Y , childSize . Width , childSize . Height ) ) ;
node . Position = childPos ;
node . Arranged = true ;
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 ;
}
}
}