[NUI] Introduce CollectionView and related classes. (#2525)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / RecyclerView / Layouter / GridLayouter.cs
1 /* Copyright (c) 2021 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 System.Collections.Generic;
18 using System.ComponentModel;
19 using Tizen.NUI.BaseComponents;
20
21 namespace Tizen.NUI.Components
22 {
23     /// <summary>
24     /// This class implements a grid box layout.
25     /// </summary>
26     [EditorBrowsable(EditorBrowsableState.Never)]
27     public class GridLayouter : ItemsLayouter
28     {
29         private CollectionView colView;
30         private Size2D sizeCandidate;
31         private int spanSize = 1;
32         private float align = 0.5f;
33         private bool hasHeader;
34         private float headerSize;
35         private bool hasFooter;
36         private float footerSize;
37         private bool isGrouped;
38         private readonly List<GroupInfo> groups = new List<GroupInfo>();
39         private float groupHeaderSize;
40         private float groupFooterSize;
41         private GroupInfo Visited;
42
43         /// <summary>
44         /// Clean up ItemsLayouter.
45         /// </summary>
46         /// <param name="view"> ItemsView of layouter. </param>
47         [EditorBrowsable(EditorBrowsableState.Never)]
48         public override void Initialize(RecyclerView view)
49         {
50             colView = view as CollectionView;
51             if (colView == null)
52             {
53                 throw new ArgumentException("GridLayouter only can be applied CollectionView.", nameof(view));
54             }
55
56             // 1. Clean Up
57             foreach (RecyclerViewItem item in VisibleItems)
58             {
59                 colView.UnrealizeItem(item, false);
60             }
61             VisibleItems.Clear();
62             groups.Clear();
63
64             FirstVisible = 0;
65             LastVisible = 0;
66
67             IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
68
69             RecyclerViewItem header = colView?.Header;
70             RecyclerViewItem footer = colView?.Footer;
71             float width, height;
72             int count = colView.InternalItemSource.Count;
73             int pureCount = count - (header ? 1 : 0) - (footer ? 1 : 0);
74
75             // 2. Get the header / footer and size deligated item and measure the size.
76             if (header != null)
77             {
78                 MeasureChild(colView, header);
79
80                 width = header.Layout != null ? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
81                 height = header.Layout != null ? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
82
83                 headerSize = IsHorizontal ? width : height;
84                 hasHeader = true;
85
86                 colView.UnrealizeItem(header);
87             }
88
89             if (footer != null)
90             {
91                 MeasureChild(colView, footer);
92
93                 width = footer.Layout != null ? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
94                 height = footer.Layout != null ? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
95
96                 footerSize = IsHorizontal ? width : height;
97                 footer.Index = count - 1;
98                 hasFooter = true;
99
100                 colView.UnrealizeItem(footer);
101             }
102
103             int firstIndex = header ? 1 : 0;
104
105             if (colView.IsGrouped)
106             {
107                 isGrouped = true;
108
109                 if (colView.GroupHeaderTemplate != null)
110                 {
111                     while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
112                     //must be always true
113                     if (colView.InternalItemSource.IsGroupHeader(firstIndex))
114                     {
115                         RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
116                         firstIndex++;
117
118                         if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
119
120                         // Need to Set proper hieght or width on scroll direciton.
121                         if (groupHeader.Layout == null)
122                         {
123                             width = groupHeader.WidthSpecification;
124                             height = groupHeader.HeightSpecification;
125                         }
126                         else
127                         {
128                             MeasureChild(colView, groupHeader);
129
130                             width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
131                             height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
132                         }
133                         //Console.WriteLine("[NUI] GroupHeader Size {0} :{0}", width, height);
134                         // pick the StepCandidate.
135                         groupHeaderSize = IsHorizontal ? width : height;
136                         colView.UnrealizeItem(groupHeader);
137                     }
138                 }
139                 else
140                 {
141                     groupHeaderSize = 0F;
142                 }
143
144                 if (colView.GroupFooterTemplate != null)
145                 {
146                     int firstFooter = firstIndex;
147                     while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
148                     //must be always true
149                     if (colView.InternalItemSource.IsGroupFooter(firstFooter))
150                     {
151                         RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
152
153                         if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
154                         // Need to Set proper hieght or width on scroll direciton.
155                         if (groupFooter.Layout == null)
156                         {
157                             width = groupFooter.WidthSpecification;
158                             height = groupFooter.HeightSpecification;
159                         }
160                         else
161                         {
162                             MeasureChild(colView, groupFooter);
163
164                             width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
165                             height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
166                         }
167                         // pick the StepCandidate.
168                         groupFooterSize = IsHorizontal ? width : height;
169
170                         colView.UnrealizeItem(groupFooter);
171                     }
172                 }
173                 else
174                 {
175                     groupFooterSize = 0F;
176                 }
177             }
178             else isGrouped = false;
179
180             bool failed = false;
181             //Final Check of FirstIndex
182             while (colView.InternalItemSource.IsHeader(firstIndex) ||
183                     colView.InternalItemSource.IsGroupHeader(firstIndex) ||
184                     colView.InternalItemSource.IsGroupFooter(firstIndex))
185             {
186                 if (colView.InternalItemSource.IsFooter(firstIndex))
187                 {
188                     StepCandidate = 0F;
189                     failed = true;
190                     break;
191                 }
192                 firstIndex++;
193             }
194
195             sizeCandidate = new Size2D(0, 0);
196             if (!failed)
197             {
198                 // Get Size Deligate. FIXME if group exist index must be changed.
199                 RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
200                 if (sizeDeligate == null)
201                 {
202                     throw new Exception("Cannot create content from DatTemplate.");
203                 }
204                 sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
205
206                 // Need to Set proper hieght or width on scroll direciton.
207                 if (sizeDeligate.Layout == null)
208                 {
209                     width = sizeDeligate.WidthSpecification;
210                     height = sizeDeligate.HeightSpecification;
211                 }
212                 else
213                 {
214                     MeasureChild(colView, sizeDeligate);
215
216                     width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
217                     height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
218                 }
219                 //Console.WriteLine("[NUI] item Size {0} :{1}", width, height);
220
221                 // pick the StepCandidate.
222                 StepCandidate = IsHorizontal ? width : height;
223                 spanSize = IsHorizontal ? Convert.ToInt32(Math.Truncate((double)(colView.Size.Height / height))) :
224                                         Convert.ToInt32(Math.Truncate((double)(colView.Size.Width / width)));
225
226                 sizeCandidate = new Size2D(Convert.ToInt32(width), Convert.ToInt32(height));
227
228                 colView.UnrealizeItem(sizeDeligate);
229             }
230
231             if (StepCandidate < 1) StepCandidate = 1;
232             if (spanSize < 1) spanSize = 1;
233
234             if (isGrouped)
235             {
236                 float Current = 0.0F;
237                 IGroupableItemSource source = colView.InternalItemSource;
238                 GroupInfo currentGroup = null;
239
240                 for (int i = 0; i < count; i++)
241                 {
242                     if (i == 0 && hasHeader)
243                     {
244                         Current += headerSize;
245                     }
246                     else if (i == count - 1 && hasFooter)
247                     {
248                         Current += footerSize;
249                     }
250                     else
251                     {
252                         //GroupHeader must always exist in group usage.
253                         if (source.IsGroupHeader(i))
254                         {
255                             currentGroup = new GroupInfo()
256                             {
257                                 GroupParent = source.GetGroupParent(i),
258                                 StartIndex = i,
259                                 Count = 1,
260                                 GroupSize = groupHeaderSize,
261                                 GroupPosition = Current
262                             };
263                             groups.Add(currentGroup);
264                             Current += groupHeaderSize;
265                         }
266                         //optional
267                         else if (source.IsGroupFooter(i))
268                         {
269                             //currentGroup.hasFooter = true;
270                             currentGroup.Count++;
271                             currentGroup.GroupSize += groupFooterSize;
272                             Current += groupFooterSize;
273                         }
274                         else
275                         {
276                             currentGroup.Count++;
277                             int index = i - currentGroup.StartIndex - 1; // groupHeader must always exist.
278                             if ((index % spanSize) == 0)
279                             {
280                                 currentGroup.GroupSize += StepCandidate;
281                                 Current += StepCandidate;
282                             }
283                         }
284                     }
285                 }
286                 ScrollContentSize = Current;
287             }
288             else
289             {
290                 // 3. Measure the scroller content size.
291                 ScrollContentSize = StepCandidate * Convert.ToInt32(Math.Ceiling((double)pureCount / (double)spanSize));
292                 if (hasHeader) ScrollContentSize += headerSize;
293                 if (hasFooter) ScrollContentSize += footerSize;
294             }
295
296             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
297             else colView.ContentContainer.SizeHeight = ScrollContentSize;
298
299             base.Initialize(colView);
300             //Console.WriteLine("Init Done, StepCnadidate{0}, spanSize{1}, Scroll{2}", StepCandidate, spanSize, ScrollContentSize);
301         }
302
303         /// <summary>
304         /// This is called to find out where items are lain out according to current scroll position.
305         /// </summary>
306         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
307         /// <param name="force">boolean force flag to layouting forcely.</param>
308         public override void RequestLayout(float scrollPosition, bool force = false)
309         {
310             // Layouting is only possible after once it intialized.
311             if (!IsInitialized) return;
312             int LastIndex = colView.InternalItemSource.Count;
313
314             if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
315             PrevScrollPosition = Math.Abs(scrollPosition);
316
317             int prevFirstVisible = FirstVisible;
318             int prevLastVisible = LastVisible;
319             bool IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
320
321             (float X, float Y) visibleArea = (PrevScrollPosition,
322                 PrevScrollPosition + (IsHorizontal ? colView.Size.Width : colView.Size.Height)
323             );
324
325             //Console.WriteLine("[NUI] itemsView [{0},{1}] [{2},{3}]", colView.Size.Width, colView.Size.Height, colView.ContentContainer.Size.Width, colView.ContentContainer.Size.Height);
326
327             // 1. Set First/Last Visible Item Index. 
328             (int start, int end) = FindVisibleItems(visibleArea);
329             FirstVisible = start;
330             LastVisible = end;
331
332             //Console.WriteLine("[NUI] {0} :visibleArea before [{1},{2}] after [{3},{4}]", scrollPosition, prevFirstVisible, prevLastVisible, FirstVisible, LastVisible);
333
334             // 2. Unrealize invisible items.
335             List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
336             foreach (RecyclerViewItem item in VisibleItems)
337             {
338                 if (item.Index < FirstVisible || item.Index > LastVisible)
339                 {
340                     //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
341                     unrealizedItems.Add(item);
342                     colView.UnrealizeItem(item);
343                 }
344             }
345             VisibleItems.RemoveAll(unrealizedItems.Contains);
346
347             //Console.WriteLine("Realize Begin [{0} to {1}]", FirstVisible, LastVisible);
348             // 3. Realize and placing visible items.
349             for (int i = FirstVisible; i <= LastVisible; i++)
350             {
351                 //Console.WriteLine("[NUI] Realize!");
352                 RecyclerViewItem item = null;
353                 // 4. Get item if visible or realize new.
354                 if (i >= prevFirstVisible && i <= prevLastVisible)
355                 {
356                     item = GetVisibleItem(i);
357                     if (item) continue;
358                 }
359                 if (item == null) item = colView.RealizeItem(i);
360                 VisibleItems.Add(item);
361
362                 (float x, float y) = GetItemPosition(i);
363                 // 5. Placing item.
364                 item.Position = new Position(x, y);
365                 //Console.WriteLine("[NUI] ["+item.Index+"] ["+item.Position.X+", "+item.Position.Y+" ==== \n");
366             }
367             //Console.WriteLine("Realize Done");
368         }
369
370         /// <Inheritdoc/>
371         [EditorBrowsable(EditorBrowsableState.Never)]
372         public override (float X, float Y) GetItemPosition(object item)
373         {
374             if (item == null) throw new ArgumentNullException(nameof(item));
375             if (colView == null) return (0, 0);
376
377             return GetItemPosition(colView.InternalItemSource.GetPosition(item));
378         }
379
380         /// <Inheritdoc/>
381         [EditorBrowsable(EditorBrowsableState.Never)]
382         public override (float X, float Y) GetItemSize(object item)
383         {
384             if (item == null) throw new ArgumentNullException(nameof(item));
385             if (sizeCandidate == null) return (0, 0);
386
387             if (isGrouped)
388             {
389                 int index = colView.InternalItemSource.GetPosition(item);
390                 float view = (IsHorizontal ? colView.Size.Height : colView.Size.Width);
391
392                 if (colView.InternalItemSource.IsGroupHeader(index))
393                 {
394                     return (IsHorizontal ? (groupHeaderSize, view) : (view, groupHeaderSize));
395                 }
396                 else if (colView.InternalItemSource.IsGroupFooter(index))
397                 {
398                     return (IsHorizontal ? (groupFooterSize, view) : (view, groupFooterSize));
399                 }
400             }
401
402             return (sizeCandidate.Width, sizeCandidate.Height);
403         }
404
405         /// <inheritdoc/>
406         public override void NotifyItemSizeChanged(RecyclerViewItem item)
407         {
408             // All Item size need to be same in grid!
409             // if you want to change item size, change dataTemplate to re-initing.
410             return;
411         }
412
413         /// <Inheritdoc/>
414         [EditorBrowsable(EditorBrowsableState.Never)]
415         public override float CalculateLayoutOrientationSize()
416         {
417             //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
418             return ScrollContentSize;
419         }
420
421         /// <Inheritdoc/>
422         [EditorBrowsable(EditorBrowsableState.Never)]
423         public override float CalculateCandidateScrollPosition(float scrollPosition)
424         {
425             //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
426             return scrollPosition;
427         }
428
429         /// <Inheritdoc/>
430         [EditorBrowsable(EditorBrowsableState.Never)]
431         public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
432         {
433             if (currentFocusedView == null)
434                 throw new ArgumentNullException(nameof(currentFocusedView));
435
436             View nextFocusedView = null;
437             int targetSibling = -1;
438             bool IsHorizontal = colView.ScrollingDirection == ScrollableBase.Direction.Horizontal;
439
440             switch (direction)
441             {
442                 case View.FocusDirection.Left:
443                     {
444                         targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
445                         break;
446                     }
447                 case View.FocusDirection.Right:
448                     {
449                         targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
450                         break;
451                     }
452                 case View.FocusDirection.Up:
453                     {
454                         targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
455                         break;
456                     }
457                 case View.FocusDirection.Down:
458                     {
459                         targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
460                         break;
461                     }
462             }
463
464             if (targetSibling > -1 && targetSibling < Container.Children.Count)
465             {
466                 RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
467                 if (candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
468                 {
469                     nextFocusedView = candidate;
470                 }
471             }
472             return nextFocusedView;
473         }
474
475         /// <inheritdoc/>
476         [EditorBrowsable(EditorBrowsableState.Never)]
477         protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
478         {
479             int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter ? 1 : 0);
480             int adds = spanSize * 2;
481             int skipGroup = -1;
482             (int start, int end) found = (0, 0);
483
484             // Header is Showing
485             if (hasHeader && visibleArea.X < headerSize)
486             {
487                 found.start = 0;
488             }
489             else
490             {
491                 if (isGrouped)
492                 {
493                     bool failed = true;
494                     foreach (GroupInfo gInfo in groups)
495                     {
496                         skipGroup++;
497                         // in the Group
498                         if (gInfo.GroupPosition <= visibleArea.X &&
499                             gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
500                         {
501                             if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.X)
502                             {
503                                 found.start = gInfo.StartIndex - adds;
504                                 failed = false;
505                             }
506                             //can be step in spanSize...
507                             for (int i = 1; i < gInfo.Count; i++)
508                             {
509                                 if (!failed) break;
510                                 // Reach last index of group.
511                                 if (i == (gInfo.Count - 1))
512                                 {
513                                     found.start = gInfo.StartIndex + i - adds;
514                                     failed = false;
515                                     break;
516
517                                 }
518                                 else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.X - gInfo.GroupPosition - groupHeaderSize)
519                                 {
520                                     found.start = gInfo.StartIndex + i - adds;
521                                     failed = false;
522                                     break;
523                                 }
524                             }
525                         }
526                     }
527                     //footer only shows?
528                     if (failed)
529                     {
530                         found.start = MaxIndex;
531                     }
532                 }
533                 else
534                 {
535                     float visibleAreaX = visibleArea.X - (hasHeader ? headerSize : 0);
536                     found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / StepCandidate)) - 1) * spanSize;
537                     if (hasHeader) found.start += 1;
538                 }
539                 if (found.start < 0) found.start = 0;
540             }
541
542             if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
543             {
544                 found.end = MaxIndex + 1;
545             }
546             else
547             {
548                 if (isGrouped)
549                 {
550                     bool failed = true;
551                     // can it be start from founded group...?
552                     //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
553                     foreach (GroupInfo gInfo in groups)
554                     {
555                         // in the Group
556                         if (gInfo.GroupPosition <= visibleArea.Y &&
557                             gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
558                         {
559                             if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.Y)
560                             {
561                                 found.end = gInfo.StartIndex + adds;
562                                 failed = false;
563                             }
564                             //can be step in spanSize...
565                             for (int i = 1; i < gInfo.Count; i++)
566                             {
567                                 if (!failed) break;
568                                 // Reach last index of group.
569                                 if (i == (gInfo.Count - 1))
570                                 {
571                                     found.end = gInfo.StartIndex + i + adds;
572                                     failed = false;
573                                     break;
574                                 }
575                                 else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.Y - gInfo.GroupPosition - groupHeaderSize)
576                                 {
577                                     found.end = gInfo.StartIndex + i + adds;
578                                     failed = false;
579                                     break;
580                                 }
581                             }
582                         }
583                     }
584                     //footer only shows?
585                     if (failed)
586                     {
587                         found.start = MaxIndex;
588                     }
589                 }
590                 else
591                 {
592                     float visibleAreaY = visibleArea.Y - (hasHeader ? headerSize : 0);
593                     //Need to Consider GroupHeight!!!!
594                     found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / StepCandidate)) + 1) * spanSize + adds;
595                     if (hasHeader) found.end += 1;
596                 }
597                 if (found.end > (MaxIndex)) found.end = MaxIndex;
598             }
599             return found;
600         }
601
602         private (float X, float Y) GetItemPosition(int index)
603         {
604             float xPos, yPos;
605             if (sizeCandidate == null) return (0, 0);
606
607             if (hasHeader && index == 0)
608             {
609                 return (0, 0);
610             }
611             if (hasFooter && index == colView.InternalItemSource.Count - 1)
612             {
613                 xPos = IsHorizontal ? ScrollContentSize - footerSize : 0;
614                 yPos = IsHorizontal ? 0 : ScrollContentSize - footerSize;
615                 return (xPos, yPos);
616             }
617             if (isGrouped)
618             {
619                 GroupInfo myGroup = GetGroupInfo(index);
620                 if (colView.InternalItemSource.IsGroupHeader(index))
621                 {
622                     xPos = IsHorizontal ? myGroup.GroupPosition : 0;
623                     yPos = IsHorizontal ? 0 : myGroup.GroupPosition;
624                 }
625                 else if (colView.InternalItemSource.IsGroupFooter(index))
626                 {
627                     xPos = IsHorizontal ? myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize : 0;
628                     yPos = IsHorizontal ? 0 : myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize;
629                 }
630                 else
631                 {
632                     int pureIndex = index - myGroup.StartIndex - 1;
633                     int division = pureIndex / spanSize;
634                     int remainder = pureIndex % spanSize;
635                     int emptyArea = IsHorizontal ? (int)(colView.Size.Height - (sizeCandidate.Height * spanSize)) :
636                                                     (int)(colView.Size.Width - (sizeCandidate.Width * spanSize));
637                     if (division < 0) division = 0;
638                     if (remainder < 0) remainder = 0;
639
640                     xPos = IsHorizontal ? division * sizeCandidate.Width + myGroup.GroupPosition + groupHeaderSize : emptyArea * align + remainder * sizeCandidate.Width;
641                     yPos = IsHorizontal ? emptyArea * align + remainder * sizeCandidate.Height : division * sizeCandidate.Height + myGroup.GroupPosition + groupHeaderSize;
642                 }
643             }
644             else
645             {
646                 int pureIndex = index - (colView.Header ? 1 : 0);
647                 // int convert must be truncate value.
648                 int division = pureIndex / spanSize;
649                 int remainder = pureIndex % spanSize;
650                 int emptyArea = IsHorizontal ? (int)(colView.Size.Height - (sizeCandidate.Height * spanSize)) :
651                                                 (int)(colView.Size.Width - (sizeCandidate.Width * spanSize));
652                 if (division < 0) division = 0;
653                 if (remainder < 0) remainder = 0;
654
655                 xPos = IsHorizontal ? division * sizeCandidate.Width + (hasHeader ? headerSize : 0) : emptyArea * align + remainder * sizeCandidate.Width;
656                 yPos = IsHorizontal ? emptyArea * align + remainder * sizeCandidate.Height : division * sizeCandidate.Height + (hasHeader ? headerSize : 0);
657             }
658
659             return (xPos, yPos);
660         }
661
662         private RecyclerViewItem GetVisibleItem(int index)
663         {
664             foreach (RecyclerViewItem item in VisibleItems)
665             {
666                 if (item.Index == index) return item;
667             }
668
669             return null;
670         }
671
672         private GroupInfo GetGroupInfo(int index)
673         {
674             if (Visited != null)
675             {
676                 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
677                     return Visited;
678             }
679             if (hasHeader && index == 0) return null;
680             foreach (GroupInfo group in groups)
681             {
682                 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
683                 {
684                     Visited = group;
685                     return group;
686                 }
687             }
688             Visited = null;
689             return null;
690         }
691
692         /*
693         private object GetGroupParent(int index)
694         {
695             if (Visited != null)
696             {
697                 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
698                 return Visited.GroupParent;
699             }
700             if (hasHeader && index == 0) return null;
701             foreach (GroupInfo group in groups)
702             {
703                 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
704                 {
705                     Visited = group;
706                     return group.GroupParent;
707                 }
708             }
709             Visited = null;
710             return null;
711         }
712         */
713
714         class GroupInfo
715         {
716             public object GroupParent;
717             public int StartIndex;
718             public int Count;
719             public float GroupSize;
720             public float GroupPosition;
721             //Items relative position from the GroupPosition
722         }
723     }
724 }