[NUI] change Column, Row default value from -1 to AutoColumn, AutoRow
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Layouting / GridLocations.cs
1 /*
2  * Copyright (c) 2019 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using Tizen.NUI.BaseComponents;
20
21 namespace Tizen.NUI
22 {
23     public partial class GridLayout
24     {
25         private Node[] hEdgeList;
26         private Node[] vEdgeList;
27
28         private int maxRowCount;
29         private int maxColumnConut;
30         private float[] hLocations;
31         private float[] vLocations;
32         private int totalHorizontalExpand = 0;
33         private int totalVerticalExpand = 0;
34
35         private GridChild[] gridChildren;
36
37         /// <summary>
38         /// The nested class to represent a node of DAG.
39         /// </summary>
40         private class Node
41         {
42             /// <summary>The start vertex with the same value as <c>Column/Row</c> child property.</summary>
43             public int Start { get; }
44
45             /// <summary>The end vertex with the same value as <c>Column+ColumnSpan/Row+RowSpan</c>.</summary>
46             public int End { get; }
47
48             /// <summary>The edge with the same value as measured width/height of child.</summary>
49             public float Edge { get; }
50
51             /// <summary>The stretch with the same value as <c>HorizontalStretch/VerticalStretch</c>.</summary>
52             public StretchFlags Stretch { get; }
53
54             /// <summary>The expanded size. It can be updated by expand calculation.</summary>
55             public float ExpandedSize { get; set; }
56
57             public Node(int vertex, int span, float edge, StretchFlags stretch)
58             {
59                 Start = vertex;
60                 End = vertex + span;
61                 Edge = edge;
62                 Stretch = stretch;
63                 ExpandedSize = 0;
64             }
65         }
66         private class GridChild
67         {
68             public LayoutItem LayoutItem { get; }
69             public Node Column { get; }
70             public Node Row { get; }
71             public GridChild(LayoutItem layoutItem, Node column, Node row)
72             {
73                 LayoutItem = layoutItem;
74                 Column = column;
75                 Row = row;
76             }
77         }
78
79         private void InitChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
80         {
81             InitChildrenData(widthMeasureSpec, heightMeasureSpec);
82
83             InitEdgeList(ref hEdgeList);
84             InitLocations(ref hLocations);
85
86             InitEdgeList(ref vEdgeList);
87             InitLocations(ref vLocations);
88         }
89
90         private void InitChildrenWithExpand(LayoutLength width, LayoutLength height)
91         {
92             if (totalHorizontalExpand > 0)
93             {
94                 ReInitLocationsWithExpand(ref hLocations, width);
95             }
96             if (totalVerticalExpand > 0)
97             {
98                 ReInitLocationsWithExpand(ref vLocations, height);
99             }
100         }
101
102         private void ReInitLocationsWithExpand(ref float[] locations, LayoutLength parentSize)
103         {
104             bool isHorizontal = (locations == hLocations);
105             Node[] edgeList = isHorizontal ? hEdgeList : vEdgeList;
106             int maxIndex = isHorizontal ? maxColumnConut : maxRowCount;
107             float space = isHorizontal ? ColumnSpacing : RowSpacing;
108             float totalExpand = isHorizontal ? totalHorizontalExpand : totalVerticalExpand;
109
110             float parentDecimalSize = parentSize.AsDecimal();
111             float maxExpandedSize = parentDecimalSize * LayoutChildren.Count;
112             float minExpandedSize = 0;
113             float newChildrenSize = locations[maxIndex] - locations[0] - space;
114
115             // No available sapce
116             if (newChildrenSize > parentDecimalSize)
117                 return;
118
119             // binary search for finding maximum expanded size.
120             while ((int)(newChildrenSize + 0.5) != (int)parentDecimalSize)
121             {
122                 float curExpandedSize = (maxExpandedSize + minExpandedSize) / 2;
123                 for (int i = 0; i < edgeList.Length; i++)
124                 {
125                     Node node = edgeList[i];
126                     // update expanded size.
127                     if (node.Stretch.HasFlag(StretchFlags.Expand))
128                         node.ExpandedSize = curExpandedSize / totalExpand;
129                 }
130
131                 // re-init locations based on updated expanded size.
132                 InitLocations(ref locations);
133                 newChildrenSize = locations[maxIndex] - locations[0] - space;
134
135                 // internal child size cannot exceed the Gridlayout size.
136                 if (newChildrenSize > parentDecimalSize)
137                 {
138                     maxExpandedSize = curExpandedSize;
139                 }
140                 else
141                 {
142                     minExpandedSize = curExpandedSize;
143                 }
144             }
145         }
146
147         private void InitChildrenData(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
148         {
149             int childCount = LayoutChildren.Count;
150             bool isHorizontal = (GridOrientation == Orientation.Horizontal);
151             int mainPivot = 0, subPivot = 0;
152             int[] pivotStack = new int[isHorizontal ? Columns : Rows];
153
154             vLocations = hLocations = null;
155             vEdgeList = hEdgeList = null;
156             gridChildren = new GridChild[childCount];
157             maxColumnConut = Columns;
158             maxRowCount = Rows;
159
160             totalVerticalExpand = 0;
161             totalHorizontalExpand = 0;
162
163             for (int i = 0; i < childCount; i++)
164             {
165                 LayoutItem item = LayoutChildren[i];
166                 View view = item?.Owner;
167                 if (view == null) continue;
168
169                 int column, columnSpan, row, rowSpan;
170                 StretchFlags verticalStretch, horizontalStretch;
171
172                 column = GetColumn(view);
173                 columnSpan = GetColumnSpan(view);
174                 row = GetRow(view);
175                 rowSpan = GetRowSpan(view);
176                 verticalStretch = GetVerticalStretch(view);
177                 horizontalStretch = GetHorizontalStretch(view);
178
179                 if (column + columnSpan > maxColumnConut || row + rowSpan > maxRowCount)
180                 {
181                     if (column + columnSpan > maxColumnConut)
182                         Tizen.Log.Error("NUI", "Column + ColumnSapn exceeds Grid Columns. Column + ColumnSpan (" + column + " + " + columnSpan + ") > Grid Columns(" + maxColumnConut + ")");
183                     else
184                         Tizen.Log.Error("NUI", "Row + RowSapn exceeds Grid Rows. Row + RowSapn (" + row + " + " + rowSpan + ") > Grid Rows(" + maxRowCount + ")");
185
186                     gridChildren[i] = new GridChild(null, new Node(0, 1, 0, 0), new Node(0, 1, 0, 0));
187
188                     continue;
189                 }
190
191                 if (horizontalStretch.HasFlag(StretchFlags.Expand))
192                     totalHorizontalExpand++;
193
194                 if (verticalStretch.HasFlag(StretchFlags.Expand))
195                     totalVerticalExpand++;
196
197                 // assign column/row depending on GridOrientation. The main axis count(Columns on Horizontal, Rows otherwise) won't be exceeded
198                 // explicit column(row) count which is assigned by Columns(Rows). but, cross axis count(Rows(Columns)) can be increased by sub axis count.
199                 if (column == AutoColumn || row == AutoRow)
200                 {
201                     (int point, int span) mainAxis = isHorizontal ? (column, columnSpan) : (row, rowSpan);
202                     (int point, int span) subAxis = isHorizontal ? (row, rowSpan) : (column, columnSpan);
203
204                     if (subAxis.point != AutoColumn && subAxis.point != AutoRow)
205                         subPivot = subAxis.point;
206                     if (mainAxis.point != AutoColumn && mainAxis.point != AutoRow)
207                         mainPivot = mainAxis.point;
208
209                     if (mainPivot + mainAxis.span > pivotStack.Length)
210                     {
211                         mainPivot = 0;
212                         subPivot++;
213                     }
214
215                     for (int n = mainPivot + mainAxis.span - 1; n >= mainPivot; n--)
216                     {
217                         if (pivotStack[n] > subPivot)
218                         {
219                             mainPivot = n + 1;
220                             n = mainPivot + mainAxis.span;
221
222                             if (n > pivotStack.Length)
223                             {
224                                 if (mainAxis.point != AutoColumn && mainAxis.point != AutoRow)
225                                     mainPivot = mainAxis.point;
226                                 else
227                                     mainPivot = 0;
228
229                                 n = mainPivot + mainAxis.span;
230                                 subPivot++;
231                             }
232                         }
233                     }
234
235                     if (isHorizontal)
236                     {
237                         column = mainPivot;
238                         row = subPivot;
239                     }
240                     else
241                     {
242                         column = subPivot;
243                         row = mainPivot;
244                     }
245
246                     for (int start = mainPivot, end = mainPivot + mainAxis.span; start < end; start++)
247                     {
248                         pivotStack[start] = subPivot + subAxis.span;
249                     }
250
251                     mainPivot += mainAxis.span;
252                 }
253
254                 if (maxColumnConut < column + columnSpan)
255                     maxColumnConut = column + columnSpan;
256                 if (maxRowCount < row + rowSpan)
257                     maxRowCount = row + rowSpan;
258
259                 MeasureChildWithMargins(item, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
260                 gridChildren[i] = new GridChild(item,
261                                                 new Node(column, columnSpan, item.MeasuredWidth.Size.AsDecimal() + item.Owner.Margin.Start + item.Owner.Margin.End, horizontalStretch),
262                                                 new Node(row, rowSpan, item.MeasuredHeight.Size.AsDecimal() + item.Owner.Margin.Top + item.Owner.Margin.Bottom, verticalStretch));
263             }
264         }
265
266         /// <summary> Initialize the edge list sorted by start vetex. </summary>
267         private void InitEdgeList(ref Node[] edgeList)
268         {
269             bool isHorizontal = (edgeList == hEdgeList);
270             int axisCount = isHorizontal ? Columns : Rows;
271
272             edgeList = new Node[gridChildren.Length + axisCount];
273
274             for (int i = 0; i < gridChildren.Length; i++)
275                 edgeList[i] = isHorizontal ? gridChildren[i].Column : gridChildren[i].Row;
276
277             // Add virtual edge that have no edge for connecting adjacent cells.
278             for (int i = LayoutChildren.Count, end = LayoutChildren.Count + axisCount, v = 0; i < end; i++, v++)
279                 edgeList[i] = new Node(v, 1, 0, 0);
280
281             Array.Sort(edgeList, (a, b) => a.Start.CompareTo(b.Start));
282         }
283
284         /// <summary>
285         /// Locations are longest path from zero-vertex. that means 'locations[MAX] - locations[0]' is maximun size of children.
286         /// Since GridLayout is Directed Acyclic Graph(DAG) which have no negative cycles, longest path can be found in linear time.
287         /// </summary>
288         private void InitLocations(ref float[] locations)
289         {
290             bool isHorizontal = (locations == hLocations);
291             int maxAxisCount = isHorizontal ? maxColumnConut : maxRowCount;
292             Node[] edgeList = isHorizontal ? hEdgeList : vEdgeList;
293             float space = isHorizontal ? ColumnSpacing : RowSpacing;
294
295             locations = new float[maxAxisCount + 1];
296
297             for (int i = 0; i < edgeList.Length; i++)
298             {
299                 float newLocation = locations[edgeList[i].Start] + edgeList[i].Edge + edgeList[i].ExpandedSize;
300                 if (edgeList[i].Edge + edgeList[i].ExpandedSize > 0)
301                     newLocation += space;
302
303                 if (locations[edgeList[i].End] < newLocation)
304                 {
305                     locations[edgeList[i].End] = newLocation;
306                 }
307             }
308         }
309     }
310 }