Follow formatting NUI
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / RecyclerView / GridRecycleLayoutManager.cs
1 /* Copyright (c) 2020 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 using System;
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20
21 namespace Tizen.NUI.Components
22 {
23     /// <summary>
24     /// [Draft] This class implements a grid box layout.
25     /// </summary>
26     /// <since_tizen> 8 </since_tizen>
27     /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
28     [EditorBrowsable(EditorBrowsableState.Never)]
29     public class GridRecycleLayoutManager : RecycleLayoutManager
30     {
31         private int rows = 1;
32
33         /// <summary>
34         /// [draft ]Get/Set the number of rows in the grid
35         /// </summary>
36         /// <since_tizen> 8 </since_tizen>
37         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
38         [EditorBrowsable(EditorBrowsableState.Never)]
39         public int Rows
40         {
41             get
42             {
43                 return rows;
44             }
45             set
46             {
47                 rows = value;
48
49                 if (Container != null)
50                 {
51                     Layout(PrevScrollPosition);
52                 }
53             }
54         }
55
56         private int columns = 1;
57
58
59         /// <summary>
60         /// [Draft] Get/Set the number of columns in the grid
61         /// </summary>
62         /// <since_tizen> 8 </since_tizen>
63         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
64         [EditorBrowsable(EditorBrowsableState.Never)]
65         public int Columns
66         {
67             get
68             {
69                 return columns;
70             }
71             set
72             {
73                 columns = value;
74
75                 if (Container != null)
76                 {
77                     Layout(PrevScrollPosition);
78                 }
79             }
80         }
81
82         private int firstVisibleItemIndex = -1;
83         private int lastVisibleItemIndex = -1;
84
85         private bool IsItemVisible(float scrollPosition, RecycleItem item)
86         {
87             bool result = false;
88             View list = Container.GetParent() as View;
89             if (list == null)
90             {
91                 return result;
92             }
93             Vector2 visibleArea = new Vector2(Math.Abs(scrollPosition),
94                 Math.Abs(scrollPosition) + (LayoutOrientation == Orientation.Vertical ?
95                                                 list.Size.Width : list.Size.Height)
96             );
97
98             float firstCheckPoint = LayoutOrientation == Orientation.Vertical ? item.Position.X : item.Position.Y;
99             float secondCheckPoint = LayoutOrientation == Orientation.Vertical ?
100                                         firstCheckPoint + item.Size.Width :
101                                         firstCheckPoint + item.Size.Height;
102
103             result = (firstCheckPoint >= visibleArea.X && firstCheckPoint <= visibleArea.Y) || (secondCheckPoint >= visibleArea.X && secondCheckPoint <= visibleArea.Y);
104
105             return result;
106         }
107
108         /// <summary>
109         /// This is called to find out how much container size can be.
110         /// </summary>
111         /// <since_tizen> 8 </since_tizen>
112         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
113         [EditorBrowsable(EditorBrowsableState.Never)]
114         public override float CalculateLayoutOrientationSize()
115         {
116             float orientationFactor = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
117             return StepSize * (int)Math.Ceiling((double)DataCount / (double)orientationFactor);
118         }
119
120
121         /// <summary>
122         /// This is called to find out where items are lain out according to current scroll position.
123         /// </summary>
124         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
125         /// <since_tizen> 8 </since_tizen>
126         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
127         public override void Layout(float scrollPosition)
128         {
129             int itemInGroup = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
130             firstVisibleItemIndex = -1;
131             lastVisibleItemIndex = -1;
132
133             RecycleItem previousItem = null;
134
135             for (int i = 0; i < Container.Children.Count; i++)
136             {
137                 RecycleItem item = Container.Children[i] as RecycleItem;
138
139                 if (previousItem != null && item != null)
140                 {
141                     item.Position = LayoutOrientation == Orientation.Vertical ?
142                         new Position(
143                             (i % itemInGroup == 0 ?
144                             previousItem.Position.X + (previousItem.CurrentSize.Width != 0 ?
145                                                             previousItem.CurrentSize.Width :
146                                                             previousItem.Size.Width) :
147                             previousItem.Position.X),
148                             (i % itemInGroup == 0 ?
149                             0 :
150                             previousItem.PositionY + (previousItem.CurrentSize.Height != 0 ?
151                                                             previousItem.CurrentSize.Height :
152                                                             previousItem.Size.Height))
153                         ) :
154                         new Position(
155                             (i % itemInGroup == 0 ?
156                             0 :
157                             previousItem.PositionX + (previousItem.CurrentSize.Width != 0 ?
158                                                             previousItem.CurrentSize.Width :
159                                                             previousItem.Size.Width)),
160                             (i % itemInGroup == 0 ?
161                             previousItem.Position.Y + (previousItem.CurrentSize.Height != 0 ?
162                                                             previousItem.CurrentSize.Height :
163                                                             previousItem.Size.Height) :
164                             previousItem.Position.Y)
165                         );
166                 }
167
168                 bool isVisible = IsItemVisible(scrollPosition, item);
169
170                 if (isVisible)
171                 {
172                     firstVisibleItemIndex = firstVisibleItemIndex == -1 ? i : firstVisibleItemIndex;
173                     lastVisibleItemIndex = i;
174                 }
175
176                 previousItem = item;
177             }
178
179             if (StepSize == 0)
180             {
181                 StepSize = LayoutOrientation == Orientation.Vertical ? ItemSize.Width : ItemSize.Height;
182             }
183         }
184
185
186         /// <summary>
187         /// This is called to find out which items should be recycled according to current scroll position.
188         /// </summary>
189         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
190         /// <returns>List of RecycleItems which should be recycled.</returns>
191         /// <since_tizen> 8 </since_tizen>
192         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
193         public override List<RecycleItem> Recycle(float scrollPosition)
194         {
195             List<RecycleItem> result = new List<RecycleItem>();
196             bool checkFront = (PrevScrollPosition - scrollPosition) > 0;
197
198             int itemInGroup = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
199
200             if (checkFront)
201             {
202                 int currentGroupNum = (int)(firstVisibleItemIndex / itemInGroup) + 1;
203
204                 if (currentGroupNum > 2)
205                 {
206                     // Too many item is in front!!! move first item to back!!!!
207                     for (int i = 0; i < itemInGroup; i++)
208                     {
209                         RecycleItem target = Container.Children[0] as RecycleItem;
210                         if (target != null)
211                         {
212                             target.DataIndex = target.DataIndex + Container.Children.Count;
213                             target.SiblingOrder = Container.Children.Count - 1;
214
215                             result.Add(target);
216                         }
217                     }
218                 }
219             }
220             else
221             {
222                 int currentGroupNum = (int)(lastVisibleItemIndex / itemInGroup) + 1;
223
224                 if (currentGroupNum < (int)(Container.Children.Count / itemInGroup) - 3)
225                 {
226                     for (int i = 0; i < itemInGroup; i++)
227                     {
228                         RecycleItem prevFirstItem = Container.Children[itemInGroup] as RecycleItem;
229                         RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem;
230                         if (prevFirstItem != null && target != null)
231                         {
232                             target.Position = new Position(
233                                 LayoutOrientation == Orientation.Vertical ? (prevFirstItem.Position.X - target.Size.Width) : prevFirstItem.Position.X,
234                                 LayoutOrientation == Orientation.Vertical ? prevFirstItem.Position.Y : (prevFirstItem.Position.Y - target.Size.Height)
235                             );
236                             target.DataIndex = target.DataIndex - Container.Children.Count;
237                             target.SiblingOrder = 0;
238
239                             result.Add(target);
240                         }
241                     }
242                 }
243             }
244
245
246             PrevScrollPosition = scrollPosition;
247
248             return result;
249         }
250
251         /// <summary>
252         /// Adjust scrolling position by own scrolling rules.
253         /// </summary>
254         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
255         /// <since_tizen> 8 </since_tizen>
256         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
257         public override float CalculateCandidateScrollPosition(float scrollPosition)
258         {
259             return scrollPosition;
260         }
261
262         public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
263         {
264             View nextFocusedView = null;
265             int targetSibling = -1;
266             bool isHorizontal = LayoutOrientation == Orientation.Horizontal;
267
268             switch (direction)
269             {
270                 case View.FocusDirection.Left:
271                     {
272                         targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - 1 : currentFocusedView.SiblingOrder - Rows;
273                         break;
274                     }
275                 case View.FocusDirection.Right:
276                     {
277                         targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + 1 : currentFocusedView.SiblingOrder + Rows;
278                         break;
279                     }
280                 case View.FocusDirection.Up:
281                     {
282                         targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - Columns : currentFocusedView.SiblingOrder - 1;
283                         break;
284                     }
285                 case View.FocusDirection.Down:
286                     {
287                         targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + Columns : currentFocusedView.SiblingOrder + 1;
288                         break;
289                     }
290             }
291
292             if (targetSibling > -1 && targetSibling < Container.Children.Count)
293             {
294                 RecycleItem candidate = Container.Children[targetSibling] as RecycleItem;
295                 if (candidate != null && candidate.DataIndex >= 0 && candidate.DataIndex < DataCount)
296                 {
297                     nextFocusedView = candidate;
298                 }
299             }
300
301             return nextFocusedView;
302         }
303     }
304 }