[NUI] Measure whole child in GridLayout (#1364)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Layouting / GridLayout.cs
1 /* Copyright (c) 2019 Samsung Electronics Co., Ltd.
2 .*
3 .* Licensed under the Apache License, Version 2.0 (the "License");
4 .* you may not use this file except in compliance with the License.
5 .* You may obtain a copy of the License at
6 .*
7 .* http://www.apache.org/licenses/LICENSE-2.0
8 .*
9 .* Unless required by applicable law or agreed to in writing, software
10 .* distributed under the License is distributed on an "AS IS" BASIS,
11 .* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 .* See the License for the specific language governing permissions and
13 .* limitations under the License.
14 .*
15 .*/
16
17 using System;
18 using System.ComponentModel;
19 using System.Collections.Generic;
20 using Tizen.NUI.BaseComponents;
21
22 namespace Tizen.NUI
23 {
24     /// <summary>
25     /// [Draft] This class implements a grid layout
26     /// </summary>
27     public class GridLayout : LayoutGroup
28     {
29         const int AUTO_FIT = -1;
30         private int _columns = 1;
31         private int _rows = 1;
32         private int _totalWidth;
33         private int _totalHeight;
34         private int _requestedColumnWidth = 1;
35         private int _numberOfRequestedColumns;
36         private GridLocations _locations;
37
38         /// <summary>
39         /// [draft] GridLayout Constructor/>
40         /// </summary>
41         /// <returns> New Grid object.</returns>
42         /// <since_tizen> 6 </since_tizen>
43         public GridLayout()
44         {
45             _locations = new GridLocations();
46         }
47
48         /// <summary>
49         /// [Draft] Get/Set the number of columns in the grid
50         /// </summary>
51         /// <since_tizen> 6 </since_tizen>
52         public int Columns
53         {
54             get
55             {
56                 return GetColumns();
57             }
58             set
59             {
60                 SetColumns(value);
61             }
62         }
63
64
65         /// <summary>
66         /// [draft ] Sets the number of columns the GridLayout should have. />
67         /// </summary>
68         /// <param name="columns">The number of columns.</param>
69         internal void SetColumns(int columns)
70         {
71             _numberOfRequestedColumns = columns;
72             if( columns != _columns)
73             {
74                 _columns = Math.Max(1, _columns);
75                 _columns = columns;
76                 RequestLayout();
77             }
78         }
79
80         /// <summary>
81         /// [draft ] Gets the number of columns in the Grid />
82         /// </summary>
83         /// <returns>The number of columns in the Grid.</returns>
84         internal int GetColumns()
85         {
86             return _columns;
87         }
88
89         void DetermineNumberOfColumns( int availableSpace )
90         {
91             if( _numberOfRequestedColumns == AUTO_FIT )
92             {
93                 if( availableSpace > 0 )
94                 {
95                     // Can only calculate number of columns if a column width has been set
96                     _columns = ( _requestedColumnWidth > 0 ) ? ( availableSpace / _requestedColumnWidth ) : 1;
97                 }
98             }
99         }
100
101         /// <summary>
102         /// Measure the layout and its content to determine the measured width and the measured height.<br />
103         /// </summary>
104         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
105         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
106         /// <since_tizen> 6 </since_tizen>
107         protected override void OnMeasure( MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec )
108         {
109             var gridWidthMode = widthMeasureSpec.Mode;
110             var gridHeightMode = heightMeasureSpec.Mode;
111             int widthSize = (int)widthMeasureSpec.Size.AsRoundedValue();
112             int heightSize = (int)heightMeasureSpec.Size.AsRoundedValue();
113
114             int availableContentWidth;
115             int availableContentHeight;
116
117             int desiredChildHeight;
118             int desiredChildWidth;
119
120             Extents gridLayoutPadding = Padding;
121
122             var childCount = LayoutChildren.Count;
123
124             // WIDTH SPECIFICATIONS
125             if (childCount > 0)
126             {
127                 foreach( LayoutItem childLayout in LayoutChildren )
128                 {
129                     if( childLayout != null )
130                     {
131                         MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
132                     }
133                 }
134
135                 // Use first child's dimensions for layout measurement
136                 View childOwner = LayoutChildren[0].Owner;
137
138                 desiredChildHeight = (int)LayoutChildren[0].MeasuredHeight.Size.AsRoundedValue();
139                 desiredChildWidth = (int)LayoutChildren[0].MeasuredWidth.Size.AsRoundedValue();
140
141                 // If child has a margin then add it to desired size
142                 Extents childMargin = LayoutChildren[0].Margin;
143                 desiredChildHeight += childMargin.Top + childMargin.Bottom;
144                 desiredChildWidth += childMargin.Start + childMargin.End;
145
146                 _totalWidth = desiredChildWidth * _columns;
147
148                 // Include padding for max and min checks
149                 _totalWidth += gridLayoutPadding.Start + gridLayoutPadding.End;
150
151                 // Ensure width does not exceed specified at most width or less than mininum width
152                 _totalWidth = Math.Max( _totalWidth, (int)SuggestedMinimumWidth.AsRoundedValue() );
153
154                 // widthMode EXACTLY so grid must be the given width
155                 if( gridWidthMode == MeasureSpecification.ModeType.Exactly || gridWidthMode == MeasureSpecification.ModeType.AtMost )
156                 {
157                     // In the case of AT_MOST, widthSize is the max limit.
158                     _totalWidth = Math.Min( _totalWidth, widthSize );
159                 }
160
161                 availableContentWidth = _totalWidth - gridLayoutPadding.Start - gridLayoutPadding.End;
162                 widthSize = _totalWidth;
163
164                 // HEIGHT SPECIFICATIONS
165
166                 // heightMode EXACTLY so grid must be the given height
167                 if( gridHeightMode == MeasureSpecification.ModeType.Exactly || gridHeightMode == MeasureSpecification.ModeType.AtMost )
168                 {
169                     if( childCount > 0 )
170                     {
171                         _totalHeight = gridLayoutPadding.Top + gridLayoutPadding.Bottom;
172
173                         for( int i = 0; i < childCount; i += _columns )
174                         {
175                           _totalHeight += desiredChildHeight;
176                         }
177
178                         // Ensure ourHeight does not exceed specified at most height
179                         _totalHeight = Math.Min( _totalHeight, heightSize );
180                         _totalHeight = Math.Max( _totalHeight, (int)SuggestedMinimumHeight.AsRoundedValue() );
181
182                         heightSize = _totalHeight;
183                     } // Child exists
184
185                     // In the case of AT_MOST, availableContentHeight is the max limit.
186                     availableContentHeight = heightSize - gridLayoutPadding.Top - gridLayoutPadding.Bottom;
187                 }
188                 else
189                 {
190                     // Grid expands to fit content
191
192                     // If number of columns AUTO_FIT then set to 1 column.
193                     _columns = ( _columns > 0 ) ? _columns : 1;
194                     // Calculate numbers of rows, round down result as later check for remainder.
195                     _rows = childCount / _columns;
196                     // If number of cells not cleanly dividable by columns, add another row to house remainder cells.
197                     _rows += ( childCount % _columns > 0 ) ? 1 : 0;
198
199                     heightSize = desiredChildHeight * _rows + gridLayoutPadding.Top + gridLayoutPadding.Bottom;
200                     availableContentHeight = heightSize - gridLayoutPadding.Top - gridLayoutPadding.Bottom;
201                 }
202
203                 // If number of columns not defined
204                 DetermineNumberOfColumns( availableContentWidth );
205
206                 // Locations define the start, end,top and bottom of each cell.
207                 _locations.CalculateLocations(_columns, availableContentWidth, availableContentHeight, childCount);
208
209             } // Children exists
210
211             SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(widthSize), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK ),
212                                    ResolveSizeAndState( new LayoutLength(heightSize), heightMeasureSpec,  MeasuredSize.StateType.MeasuredSizeOK ) );
213         }
214
215         /// <summary>
216         /// Assign a size and position to each of its children.<br />
217         /// </summary>
218         /// <param name="changed">This is a new size or position for this layout.</param>
219         /// <param name="left">Left position, relative to parent.</param>
220         /// <param name="top"> Top position, relative to parent.</param>
221         /// <param name="right">Right position, relative to parent.</param>
222         /// <param name="bottom">Bottom position, relative to parent.</param>
223         /// <since_tizen> 6 </since_tizen>
224         protected override void OnLayout( bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
225         {
226             List<GridLocations.Cell> locations = _locations.GetLocations();
227
228             Extents gridLayoutPadding = Padding;
229             Extents childMargins = new Extents();
230
231             // Margin for all children dependant on if set on first child
232             if( LayoutChildren.Count > 0 )
233             {
234               childMargins = LayoutChildren[0]?.Margin;
235             }
236
237             int index = 0;
238             foreach( LayoutItem childLayout in LayoutChildren )
239             {
240                 // for each child
241                 if( childLayout != null )
242                 {
243                     // Get start and end position of child x1,x2
244                     int x1 = locations[ index ].Start;
245                     int x2 = locations[ index ].End;
246
247                     // Get top and bottom position of child y1,y2
248                     int y1 = locations[ index ].Top;
249                     int y2 = locations[ index ].Bottom;
250
251                     // Offset children by the grids padding if present
252                     x1 += gridLayoutPadding.Start;
253                     x2 += gridLayoutPadding.Start;
254                     y1 += gridLayoutPadding.Top;
255                     y2 += gridLayoutPadding.Top;
256
257                     // Offset children by the margin of the first child ( if required ).
258                     x1 += childMargins.Start;
259                     x2 -= childMargins.End;
260                     y1 += childMargins.Top;
261                     y2 -= childMargins.Bottom;
262
263                     childLayout.Layout( new LayoutLength(x1), new LayoutLength(y1),
264                                         new LayoutLength(x2), new LayoutLength(y2) );
265                     index++;
266                 }
267             }
268         }
269     }
270 }