[NUI] Fixed GridLayout scrolling issue (#1322)
[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
126             // measure first child and use it's dimensions for layout measurement
127
128             if (childCount > 0)
129             {
130                 LayoutItem childLayoutItem = LayoutChildren[0];
131                 View childOwner = childLayoutItem.Owner;
132
133                 MeasureChild( childLayoutItem, widthMeasureSpec, heightMeasureSpec );
134                 desiredChildHeight = (int)childLayoutItem.MeasuredHeight.Size.AsRoundedValue();
135                 desiredChildWidth = (int)childLayoutItem.MeasuredWidth.Size.AsRoundedValue();
136
137                 // If child has a margin then add it to desired size
138                 Extents childMargin = childLayoutItem.Margin;
139                 desiredChildHeight += childMargin.Top + childMargin.Bottom;
140                 desiredChildWidth += childMargin.Start + childMargin.End;
141
142                 _totalWidth = desiredChildWidth * _columns;
143
144                 // Include padding for max and min checks
145                 _totalWidth += gridLayoutPadding.Start + gridLayoutPadding.End;
146
147                 // Ensure width does not exceed specified at most width or less than mininum width
148                 _totalWidth = Math.Max( _totalWidth, (int)SuggestedMinimumWidth.AsRoundedValue() );
149
150                 // widthMode EXACTLY so grid must be the given width
151                 if( gridWidthMode == MeasureSpecification.ModeType.Exactly || gridWidthMode == MeasureSpecification.ModeType.AtMost )
152                 {
153                     // In the case of AT_MOST, widthSize is the max limit.
154                     _totalWidth = Math.Min( _totalWidth, widthSize );
155                 }
156
157                 availableContentWidth = _totalWidth - gridLayoutPadding.Start - gridLayoutPadding.End;
158                 widthSize = _totalWidth;
159
160                 // HEIGHT SPECIFICATIONS
161
162                 // heightMode EXACTLY so grid must be the given height
163                 if( gridHeightMode == MeasureSpecification.ModeType.Exactly || gridHeightMode == MeasureSpecification.ModeType.AtMost )
164                 {
165                     if( childCount > 0 )
166                     {
167                         _totalHeight = gridLayoutPadding.Top + gridLayoutPadding.Bottom;
168
169                         for( int i = 0; i < childCount; i += _columns )
170                         {
171                           _totalHeight += desiredChildHeight;
172                         }
173
174                         // Ensure ourHeight does not exceed specified at most height
175                         _totalHeight = Math.Min( _totalHeight, heightSize );
176                         _totalHeight = Math.Max( _totalHeight, (int)SuggestedMinimumHeight.AsRoundedValue() );
177
178                         heightSize = _totalHeight;
179                     } // Child exists
180
181                     // In the case of AT_MOST, availableContentHeight is the max limit.
182                     availableContentHeight = heightSize - gridLayoutPadding.Top - gridLayoutPadding.Bottom;
183                 }
184                 else
185                 {
186                     // Grid expands to fit content
187
188                     // If number of columns AUTO_FIT then set to 1 column.
189                     _columns = ( _columns > 0 ) ? _columns : 1;
190                     // Calculate numbers of rows, round down result as later check for remainder.
191                     _rows = childCount / _columns;
192                     // If number of cells not cleanly dividable by columns, add another row to house remainder cells.
193                     _rows += ( childCount % _columns > 0 ) ? 1 : 0;
194
195                     heightSize = desiredChildHeight * _rows + gridLayoutPadding.Top + gridLayoutPadding.Bottom;
196                     availableContentHeight = heightSize - gridLayoutPadding.Top - gridLayoutPadding.Bottom;
197                 }
198
199                 // If number of columns not defined
200                 DetermineNumberOfColumns( availableContentWidth );
201
202                 // Locations define the start, end,top and bottom of each cell.
203                 _locations.CalculateLocations(_columns, availableContentWidth, availableContentHeight, childCount);
204
205             } // Children exists
206
207             SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(widthSize), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK ),
208                                    ResolveSizeAndState( new LayoutLength(heightSize), heightMeasureSpec,  MeasuredSize.StateType.MeasuredSizeOK ) );
209         }
210
211         /// <summary>
212         /// Assign a size and position to each of its children.<br />
213         /// </summary>
214         /// <param name="changed">This is a new size or position for this layout.</param>
215         /// <param name="left">Left position, relative to parent.</param>
216         /// <param name="top"> Top position, relative to parent.</param>
217         /// <param name="right">Right position, relative to parent.</param>
218         /// <param name="bottom">Bottom position, relative to parent.</param>
219         /// <since_tizen> 6 </since_tizen>
220         protected override void OnLayout( bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
221         {
222             List<GridLocations.Cell> locations = _locations.GetLocations();
223
224             Extents gridLayoutPadding = Padding;
225             Extents childMargins = new Extents();
226
227             // Margin for all children dependant on if set on first child
228             if( LayoutChildren.Count > 0 )
229             {
230               childMargins = LayoutChildren[0]?.Margin;
231             }
232
233             int index = 0;
234             foreach( LayoutItem childLayout in LayoutChildren )
235             {
236                 // for each child
237                 if( childLayout != null )
238                 {
239                     // Get start and end position of child x1,x2
240                     int x1 = locations[ index ].Start;
241                     int x2 = locations[ index ].End;
242
243                     // Get top and bottom position of child y1,y2
244                     int y1 = locations[ index ].Top;
245                     int y2 = locations[ index ].Bottom;
246
247                     // Offset children by the grids padding if present
248                     x1 += gridLayoutPadding.Start;
249                     x2 += gridLayoutPadding.Start;
250                     y1 += gridLayoutPadding.Top;
251                     y2 += gridLayoutPadding.Top;
252
253                     // Offset children by the margin of the first child ( if required ).
254                     x1 += childMargins.Start;
255                     x2 -= childMargins.End;
256                     y1 += childMargins.Top;
257                     y2 -= childMargins.Bottom;
258
259                     childLayout.Layout( new LayoutLength(x1), new LayoutLength(y1),
260                                         new LayoutLength(x2), new LayoutLength(y2) );
261                     index++;
262                 }
263             }
264         }
265     }
266 }