@@ -14,15 +14,6 @@ public partial class EqualPanel : Panel
1414 private double _maxItemWidth = 0 ;
1515 private double _maxItemHeight = 0 ;
1616 private int _visibleItemsCount = 0 ;
17-
18- /// <summary>
19- /// Gets or sets the spacing between items.
20- /// </summary>
21- public double Spacing
22- {
23- get { return ( double ) GetValue ( SpacingProperty ) ; }
24- set { SetValue ( SpacingProperty , value ) ; }
25- }
2617
2718 /// <summary>
2819 /// Identifies the Spacing dependency property.
@@ -32,14 +23,41 @@ public double Spacing
3223 nameof ( Spacing ) ,
3324 typeof ( double ) ,
3425 typeof ( EqualPanel ) ,
35- new PropertyMetadata ( default ( double ) , OnSpacingChanged ) ) ;
26+ new PropertyMetadata ( default ( double ) , OnEqualPanelPropertyChanged ) ) ;
27+
28+ /// <summary>
29+ /// Backing <see cref="DependencyProperty"/> for the <see cref="Orientation"/> property.
30+ /// </summary>
31+ public static readonly DependencyProperty OrientationProperty = DependencyProperty . Register (
32+ nameof ( Orientation ) ,
33+ typeof ( Orientation ) ,
34+ typeof ( EqualPanel ) ,
35+ new PropertyMetadata ( default ( Orientation ) , OnEqualPanelPropertyChanged ) ) ;
36+
37+ /// <summary>
38+ /// Gets or sets the spacing between items.
39+ /// </summary>
40+ public double Spacing
41+ {
42+ get => ( double ) GetValue ( SpacingProperty ) ;
43+ set => SetValue ( SpacingProperty , value ) ;
44+ }
45+
46+ /// <summary>
47+ /// Gets or sets the panel orientation.
48+ /// </summary>
49+ public Orientation Orientation
50+ {
51+ get => ( Orientation ) GetValue ( OrientationProperty ) ;
52+ set => SetValue ( OrientationProperty , value ) ;
53+ }
3654
3755 /// <summary>
3856 /// Creates a new instance of the <see cref="EqualPanel"/> class.
3957 /// </summary>
4058 public EqualPanel ( )
4159 {
42- RegisterPropertyChangedCallback ( HorizontalAlignmentProperty , OnHorizontalAlignmentChanged ) ;
60+ RegisterPropertyChangedCallback ( HorizontalAlignmentProperty , OnAlignmentChanged ) ;
4361 }
4462
4563 /// <inheritdoc/>
@@ -58,56 +76,129 @@ protected override Size MeasureOverride(Size availableSize)
5876 _maxItemHeight = Math . Max ( _maxItemHeight , child . DesiredSize . Height ) ;
5977 }
6078
61- if ( _visibleItemsCount > 0 )
79+ // No children, no space taken
80+ if ( _visibleItemsCount <= 0 )
81+ return new Size ( 0 , 0 ) ;
82+
83+ // Determine if the desired alignment is stretched.
84+ // Don't stretch if infinite space is available though. Attempting to divide infinite space will result in a crash.
85+ bool stretch = Orientation switch
6286 {
63- // Return equal widths based on the widest item
64- // In very specific edge cases the AvailableWidth might be infinite resulting in a crash.
65- if ( HorizontalAlignment != HorizontalAlignment . Stretch || double . IsInfinity ( availableSize . Width ) )
66- {
67- return new Size ( ( _maxItemWidth * _visibleItemsCount ) + ( Spacing * ( _visibleItemsCount - 1 ) ) , _maxItemHeight ) ;
68- }
69- else
70- {
71- // Equal columns based on the available width, adjust for spacing
72- double totalWidth = availableSize . Width - ( Spacing * ( _visibleItemsCount - 1 ) ) ;
73- _maxItemWidth = totalWidth / _visibleItemsCount ;
74- return new Size ( availableSize . Width , _maxItemHeight ) ;
75- }
87+ Orientation . Horizontal => HorizontalAlignment is HorizontalAlignment . Stretch && ! double . IsInfinity ( availableSize . Width ) ,
88+ Orientation . Vertical or _ => VerticalAlignment is VerticalAlignment . Stretch && ! double . IsInfinity ( availableSize . Height ) ,
89+ } ;
90+
91+ // Define UV coords for orientation agnostic XY manipulation
92+ var uvSize = new UVCoord ( 0 , 0 , Orientation ) ;
93+ var maxItemSize = new UVCoord ( _maxItemWidth , _maxItemHeight , Orientation ) ;
94+ double availableU = Orientation is Orientation . Horizontal ? availableSize . Width : availableSize . Height ;
95+
96+ if ( stretch )
97+ {
98+ // Adjust maxItemU to form equal rows/columns by available U space (adjust for spacing)
99+ double totalU = availableU - ( Spacing * ( _visibleItemsCount - 1 ) ) ;
100+ maxItemSize . U = totalU / _visibleItemsCount ;
101+
102+ // Set uSize/vSize for XY result construction
103+ uvSize . U = availableU ;
104+ uvSize . V = maxItemSize . V ;
76105 }
77106 else
78107 {
79- return new Size ( 0 , 0 ) ;
108+ uvSize . U = ( maxItemSize . U * _visibleItemsCount ) + ( Spacing * ( _visibleItemsCount - 1 ) ) ;
109+ uvSize . V = maxItemSize . V ;
80110 }
111+
112+ return new Size ( uvSize . X , uvSize . Y ) ;
81113 }
82114
83115 /// <inheritdoc/>
84116 protected override Size ArrangeOverride ( Size finalSize )
85117 {
86- double x = 0 ;
87-
118+ // Define UV axis
119+ var pos = new UVCoord ( 0 , 0 , Orientation ) ;
120+ ref double maxItemU = ref _maxItemWidth ;
121+ double finalSizeU = finalSize . Width ;
122+ if ( Orientation is Orientation . Vertical )
123+ {
124+ maxItemU = ref _maxItemHeight ;
125+ finalSizeU = finalSize . Height ;
126+ }
127+
88128 // Check if there's more (little) width available - if so, set max item width to the maximum possible as we have an almost perfect height.
89- if ( finalSize . Width > _visibleItemsCount * _maxItemWidth + ( Spacing * ( _visibleItemsCount - 1 ) ) )
129+ if ( finalSizeU > _visibleItemsCount * maxItemU + ( Spacing * ( _visibleItemsCount - 1 ) ) )
90130 {
91- _maxItemWidth = ( finalSize . Width - ( Spacing * ( _visibleItemsCount - 1 ) ) ) / _visibleItemsCount ;
131+ maxItemU = ( finalSizeU - ( Spacing * ( _visibleItemsCount - 1 ) ) ) / _visibleItemsCount ;
92132 }
93133
94134 var elements = Children . Where ( static e => e . Visibility == Visibility . Visible ) ;
95135 foreach ( var child in elements )
96136 {
97- child . Arrange ( new Rect ( x , 0 , _maxItemWidth , _maxItemHeight ) ) ;
98- x += _maxItemWidth + Spacing ;
137+ // NOTE: The arrange method is still in X/Y coordinate system
138+ child . Arrange ( new Rect ( pos . X , pos . Y , _maxItemWidth , _maxItemHeight ) ) ;
139+ pos . U += maxItemU + Spacing ;
99140 }
100141 return finalSize ;
101142 }
102143
103- private void OnHorizontalAlignmentChanged ( DependencyObject sender , DependencyProperty dp )
144+ private void OnAlignmentChanged ( DependencyObject sender , DependencyProperty dp )
104145 {
105146 InvalidateMeasure ( ) ;
106147 }
107148
108- private static void OnSpacingChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e )
149+ private static void OnEqualPanelPropertyChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e )
109150 {
110151 var panel = ( EqualPanel ) d ;
111152 panel . InvalidateMeasure ( ) ;
112153 }
154+
155+ /// <summary>
156+ /// A struct for mapping X/Y coordinates to an orientation adjusted U/V coordinate system.
157+ /// </summary>
158+ private struct UVCoord ( double x , double y , Orientation orientation )
159+ {
160+ private readonly bool _horizontal = orientation is Orientation . Horizontal ;
161+
162+ public UVCoord ( Size size , Orientation orientation ) : this ( size . Width , size . Height , orientation )
163+ {
164+ }
165+
166+ public double X { get ; set ; } = x ;
167+
168+ public double Y { get ; set ; } = y ;
169+
170+ public double U
171+ {
172+ readonly get => _horizontal ? X : Y ;
173+ set
174+ {
175+ if ( _horizontal )
176+ {
177+ X = value ;
178+ }
179+ else
180+ {
181+ Y = value ;
182+ }
183+ }
184+ }
185+
186+ public double V
187+ {
188+ readonly get => _horizontal ? Y : X ;
189+ set
190+ {
191+ if ( _horizontal )
192+ {
193+ Y = value ;
194+ }
195+ else
196+ {
197+ X = value ;
198+ }
199+ }
200+ }
201+
202+ public readonly Size Size => new ( X , Y ) ;
203+ }
113204}
0 commit comments