1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Windows ;
5+ using System . Windows . Controls ;
6+
7+ namespace OpenSilver . ControlsKit
8+ {
9+ /// <summary>
10+ /// A responsive <see cref="Panel"/> that dynamically switches between layouts based on its width:
11+ /// when its width meets or exceeds <c>NoColumnsBelowWidth</c>, children are arranged into equal-width columns (one per visible child);
12+ /// otherwise, they are stacked vertically in a single column.
13+ /// Child horizontal and vertical alignments are respected within each allocated slot.
14+ /// </summary>
15+ public class AdaptiveColumnsPanel : Panel
16+ {
17+ /// <summary>
18+ /// When the available width is ≤ this threshold, children stack vertically.
19+ /// When > this threshold, children lay out in N equal-width columns (N = # of children).
20+ /// </summary>
21+ public static readonly DependencyProperty NoColumnsBelowWidthProperty =
22+ DependencyProperty . Register (
23+ nameof ( NoColumnsBelowWidth ) ,
24+ typeof ( double ) ,
25+ typeof ( AdaptiveColumnsPanel ) ,
26+ new FrameworkPropertyMetadata (
27+ 500d ,
28+ FrameworkPropertyMetadataOptions . AffectsMeasure ) ) ;
29+
30+ /// <summary>
31+ /// When the available width is ≤ this threshold, children stack vertically.
32+ /// When > this threshold, children lay out in N equal-width columns (N = # of children).
33+ /// </summary>
34+ public double NoColumnsBelowWidth
35+ {
36+ get => ( double ) GetValue ( NoColumnsBelowWidthProperty ) ;
37+ set => SetValue ( NoColumnsBelowWidthProperty , value ) ;
38+ }
39+
40+ // Get visible children only once and as FrameworkElement directly
41+ private List < FrameworkElement > GetVisibleChildren ( ) =>
42+ Children . OfType < FrameworkElement > ( )
43+ . Where ( c => c . Visibility != Visibility . Collapsed )
44+ . ToList ( ) ;
45+
46+ // Determine layout mode in one place
47+ private bool ShouldUseColumns ( double availableWidth , int childCount ) =>
48+ ! double . IsInfinity ( availableWidth ) &&
49+ availableWidth > NoColumnsBelowWidth &&
50+ childCount > 0 ;
51+
52+ protected override Size MeasureOverride ( Size availableSize )
53+ {
54+ double layoutWidth = double . IsNaN ( this . Width ) ? availableSize . Width : this . Width ;
55+ var children = GetVisibleChildren ( ) ;
56+ int count = children . Count ;
57+ if ( count == 0 )
58+ {
59+ return base . MeasureOverride ( availableSize ) ;
60+ }
61+
62+ bool useColumns = ShouldUseColumns ( layoutWidth , count ) ;
63+
64+ if ( ! useColumns )
65+ {
66+ // Vertical stack mode
67+ double desiredW = 0 , desiredH = 0 ;
68+ foreach ( var child in children )
69+ {
70+ child . Measure ( new Size ( layoutWidth , double . PositiveInfinity ) ) ;
71+ double mW = child . Margin . Left + child . Margin . Right ;
72+ double mH = child . Margin . Top + child . Margin . Bottom ;
73+ desiredW = Math . Max ( desiredW , child . DesiredSize . Width + mW ) ;
74+ desiredH += child . DesiredSize . Height + mH ;
75+ }
76+ return new Size ( desiredW , desiredH ) ;
77+ }
78+ else
79+ {
80+ // Column mode
81+ double colW = layoutWidth / count ;
82+ double maxChildH = 0 ;
83+ foreach ( var child in children )
84+ {
85+ child . Measure ( new Size ( colW , double . PositiveInfinity ) ) ;
86+ double mH = child . Margin . Top + child . Margin . Bottom ;
87+ maxChildH = Math . Max ( maxChildH , child . DesiredSize . Height + mH ) ;
88+ }
89+ return new Size ( layoutWidth , maxChildH ) ;
90+ }
91+ }
92+
93+ protected override Size ArrangeOverride ( Size finalSize )
94+ {
95+ var children = GetVisibleChildren ( ) ;
96+ int count = children . Count ;
97+ if ( count == 0 )
98+ {
99+ return base . ArrangeOverride ( finalSize ) ;
100+ }
101+ bool useColumns = ShouldUseColumns ( finalSize . Width , count ) ;
102+
103+ if ( ! useColumns )
104+ {
105+ // Vertical stack mode
106+ double y = 0 ;
107+ foreach ( var child in children )
108+ {
109+ // Account for margins
110+ double marginLeft = child . Margin . Left ;
111+ double marginRight = child . Margin . Right ;
112+ double marginTop = child . Margin . Top ;
113+ double marginBottom = child . Margin . Bottom ;
114+
115+ // Calculate available width for this child
116+ double availableWidth = finalSize . Width - marginLeft - marginRight ;
117+
118+ // Determine width based on alignment
119+ double width = ( child . HorizontalAlignment == HorizontalAlignment . Stretch )
120+ ? availableWidth
121+ : Math . Min ( child . DesiredSize . Width , availableWidth ) ;
122+
123+ // Calculate x position with alignment
124+ double x = marginLeft + GetHorizontalAlignmentOffset ( availableWidth , width , child . HorizontalAlignment ) ;
125+
126+ // Arrange the child
127+ child . Arrange ( new Rect ( x , y + marginTop , width , child . DesiredSize . Height ) ) ;
128+
129+ // Move to next vertical position
130+ y += child . DesiredSize . Height + marginTop + marginBottom ;
131+ }
132+ }
133+ else
134+ {
135+ // Column mode - calculate actual height needed
136+ double maxChildH = 0 ;
137+ foreach ( var child in children )
138+ {
139+ double mH = child . Margin . Top + child . Margin . Bottom ;
140+ maxChildH = Math . Max ( maxChildH , child . DesiredSize . Height + mH ) ;
141+ }
142+
143+ // Column width
144+ double colW = finalSize . Width / count ;
145+
146+ for ( int i = 0 ; i < count ; i ++ )
147+ {
148+ var child = children [ i ] ;
149+ double marginLeft = child . Margin . Left ;
150+ double marginRight = child . Margin . Right ;
151+ double marginTop = child . Margin . Top ;
152+ double marginBottom = child . Margin . Bottom ;
153+
154+ // Available width for this column
155+ double availableWidth = colW - marginLeft - marginRight ;
156+
157+ // Determine width based on alignment
158+ double width = ( child . HorizontalAlignment == HorizontalAlignment . Stretch )
159+ ? availableWidth
160+ : Math . Min ( child . DesiredSize . Width , availableWidth ) ;
161+
162+ // Calculate horizontal position
163+ double x = ( i * colW ) + marginLeft +
164+ GetHorizontalAlignmentOffset ( availableWidth , width , child . HorizontalAlignment ) ;
165+
166+ // Calculate height based on alignment
167+ double height = ( child . VerticalAlignment == VerticalAlignment . Stretch )
168+ ? maxChildH - marginTop - marginBottom
169+ : child . DesiredSize . Height ;
170+
171+ // Calculate vertical position
172+ double availableHeight = maxChildH - marginTop - marginBottom ;
173+ double y = marginTop + GetVerticalAlignmentOffset ( availableHeight , height , child . VerticalAlignment ) ;
174+
175+ // Arrange the child
176+ child . Arrange ( new Rect ( x , y , width , height ) ) ;
177+ }
178+
179+ // Return the correct size
180+ return new Size ( finalSize . Width , maxChildH ) ;
181+ }
182+
183+ return finalSize ;
184+ }
185+
186+ private double GetHorizontalAlignmentOffset ( double container , double element , HorizontalAlignment align )
187+ {
188+ return align switch
189+ {
190+ HorizontalAlignment . Center => ( container - element ) / 2 ,
191+ HorizontalAlignment . Right => container - element ,
192+ _ => 0 // Left or Stretch
193+ } ;
194+ }
195+
196+ private double GetVerticalAlignmentOffset ( double container , double element , VerticalAlignment align )
197+ {
198+ return align switch
199+ {
200+ VerticalAlignment . Center => ( container - element ) / 2 ,
201+ VerticalAlignment . Bottom => container - element ,
202+ _ => 0 // Top or Stretch
203+ } ;
204+ }
205+ }
206+ }
0 commit comments