[NUI] enable groups regardless of groupHeader (#3199)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / RecyclerView / Layouter / LinearLayouter.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 Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20
21 namespace Tizen.NUI.Components
22 {
23     /// <summary>
24     /// layouter for CollectionView to display items in linear layout.
25     /// </summary>
26     /// <since_tizen> 9 </since_tizen>
27     public class LinearLayouter : ItemsLayouter
28     {
29         private readonly List<float> ItemPosition = new List<float>();
30         private readonly List<float> ItemSize = new List<float>();
31         private int ItemSizeChanged = -1;
32         private CollectionView colView;
33         private bool hasHeader;
34         private float headerSize;
35         private Extents headerMargin;
36         private bool hasFooter;
37         private float footerSize;
38         private Extents footerMargin;
39         private bool isGrouped;
40         private readonly List<GroupInfo> groups = new List<GroupInfo>();
41         private float groupHeaderSize;
42         private Extents groupHeaderMargin;
43         private float groupFooterSize;
44         private Extents groupFooterMargin;
45         private GroupInfo Visited;
46         private Timer requestLayoutTimer = null;
47         private bool isSourceEmpty;
48
49         /// <summary>
50         /// Clean up ItemsLayouter.
51         /// </summary>
52         /// <param name="view"> CollectionView of layouter.</param>
53         /// <remarks>please note that, view must be type of CollectionView</remarks>
54         /// <since_tizen> 9 </since_tizen>
55         public override void Initialize(RecyclerView view)
56         {
57             colView = view as CollectionView;
58             if (colView == null)
59             {
60                 throw new ArgumentException("LinearLayouter only can be applied CollectionView.", nameof(view));
61             }
62             // 1. Clean Up
63             Clear();
64
65             FirstVisible = 0;
66             LastVisible = 0;
67
68             IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
69
70             RecyclerViewItem header = colView?.Header;
71             RecyclerViewItem footer = colView?.Footer;
72             float width, height;
73             int count = colView.InternalItemSource.Count;
74
75             if (header != null)
76             {
77                 MeasureChild(colView, header);
78
79                 width = header.Layout != null? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
80                 height = header.Layout != null? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
81
82                 Extents itemMargin = header.Margin;
83                 headerSize = IsHorizontal?
84                                 width + itemMargin.Start + itemMargin.End:
85                                 height + itemMargin.Top + itemMargin.Bottom;
86                 headerMargin = new Extents(itemMargin);
87                 hasHeader = true;
88
89                 colView.UnrealizeItem(header);
90             }
91             else hasHeader = false;
92
93             if (footer != null)
94             {
95                 MeasureChild(colView, footer);
96
97                 width = footer.Layout != null? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
98                 height = footer.Layout != null? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
99
100                 Extents itemMargin = footer.Margin;
101                 footerSize = IsHorizontal?
102                                 width + itemMargin.Start + itemMargin.End:
103                                 height + itemMargin.Top + itemMargin.Bottom;
104                 footerMargin = new Extents(itemMargin);
105                 footer.Index = count - 1;
106                 hasFooter = true;
107
108                 colView.UnrealizeItem(footer);
109             }
110             else hasFooter = false;
111
112             //No Internal Source exist.
113             if (count == (hasHeader? (hasFooter? 2 : 1) : 0))
114             {
115                 isSourceEmpty = true;
116                 return;
117             }
118             isSourceEmpty = false;
119
120             int firstIndex = hasHeader? 1 : 0;
121
122             if (colView.IsGrouped)
123             {
124                 isGrouped = true;
125
126                 if (colView.GroupHeaderTemplate != null)
127                 {
128                     while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
129                     //must be always true
130                     if (colView.InternalItemSource.IsGroupHeader(firstIndex))
131                     {
132                         RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
133                         firstIndex++;
134
135                         if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
136
137                         // Need to Set proper height or width on scroll direction.
138                         if (groupHeader.Layout == null)
139                         {
140                             width = groupHeader.WidthSpecification;
141                             height = groupHeader.HeightSpecification;
142                         }
143                         else
144                         {
145                             MeasureChild(colView, groupHeader);
146
147                             width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
148                             height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
149                         }
150                         // pick the StepCandidate.
151                         Extents itemMargin = groupHeader.Margin;
152                         groupHeaderSize = IsHorizontal?
153                                             width + itemMargin.Start + itemMargin.End:
154                                             height + itemMargin.Top + itemMargin.Bottom;
155                         groupHeaderMargin = new Extents(itemMargin);
156                         colView.UnrealizeItem(groupHeader);
157                     }
158                 }
159                 else
160                 {
161                     groupHeaderSize = 0F;
162                 }
163
164                 if (colView.GroupFooterTemplate != null)
165                 {
166                     int firstFooter = firstIndex;
167                     while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
168                     //must be always true
169                     if (colView.InternalItemSource.IsGroupFooter(firstFooter))
170                     {
171                         RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
172
173                         if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
174                         // Need to Set proper height or width on scroll direction.
175                         if (groupFooter.Layout == null)
176                         {
177                             width = groupFooter.WidthSpecification;
178                             height = groupFooter.HeightSpecification;
179                         }
180                         else
181                         {
182                             MeasureChild(colView, groupFooter);
183
184                             width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
185                             height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
186                         }
187                         // pick the StepCandidate.
188                         Extents itemMargin = groupFooter.Margin;
189                         groupFooterSize = IsHorizontal?
190                                             width + itemMargin.Start + itemMargin.End:
191                                             height + itemMargin.Top + itemMargin.Bottom;
192                         groupFooterMargin = new Extents(itemMargin);  
193                         colView.UnrealizeItem(groupFooter);
194                     }
195                 }
196                 else
197                 {
198                     groupFooterSize = 0F;
199                 }
200             }
201             else isGrouped = false;
202
203             bool failed = false;
204             //Final Check of FirstIndex
205             while (colView.InternalItemSource.IsHeader(firstIndex) ||
206                     colView.InternalItemSource.IsGroupHeader(firstIndex) ||
207                     colView.InternalItemSource.IsGroupFooter(firstIndex))
208             {
209                 if (colView.InternalItemSource.IsFooter(firstIndex))
210                 {
211                     StepCandidate = 0F;
212                     failed = true;
213                     break;
214                 }
215                 firstIndex++;
216             }
217
218             if (!failed)
219             {
220                 RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
221                 if (sizeDeligate == null)
222                 {
223                     // error !
224                     throw new Exception("Cannot create content from DatTemplate.");
225                 }
226
227                 sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
228
229                 // Need to Set proper height or width on scroll direction.
230                 if (sizeDeligate.Layout == null)
231                 {
232                     width = sizeDeligate.WidthSpecification;
233                     height = sizeDeligate.HeightSpecification;
234                 }
235                 else
236                 {
237                     MeasureChild(colView, sizeDeligate);
238
239                     width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
240                     height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
241                 }
242                 // pick the StepCandidate.
243                 Extents itemMargin = sizeDeligate.Margin;
244                 StepCandidate = IsHorizontal?
245                                 width + itemMargin.Start + itemMargin.End:
246                                 height + itemMargin.Top + itemMargin.Bottom;
247                 CandidateMargin = new Extents(itemMargin);  
248                 if (StepCandidate == 0) StepCandidate = 1; //????
249
250                 colView.UnrealizeItem(sizeDeligate);
251             }
252
253             float Current = IsHorizontal? Padding.Start : Padding.Top;
254             IGroupableItemSource source = colView.InternalItemSource;
255             GroupInfo currentGroup = null;
256             object currentParent = null;
257             for (int i = 0; i < count; i++)
258             {
259                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
260                 {
261                     if (i == 0 && hasHeader)
262                         ItemSize.Add(headerSize);
263                     else if (i == count - 1 && hasFooter)
264                         ItemSize.Add(footerSize);
265                     else if (source.IsGroupHeader(i))
266                         ItemSize.Add(groupHeaderSize);
267                     else if (source.IsGroupFooter(i))
268                         ItemSize.Add(groupFooterSize);
269                     else ItemSize.Add(StepCandidate);
270                 }
271                 if (isGrouped)
272                 {
273                     if (source.IsHeader(i))
274                     {
275                         //ItemPosition.Add(Current);
276                         Current += headerSize;
277                     }
278                     else if (source.IsFooter(i))
279                     {
280                         //ItemPosition.Add(Current);
281                         Current += footerSize;
282                     }
283                     else
284                     {
285                         //GroupHeader must always exist in group usage.
286                         //if (source.IsGroupHeader(i))
287                         if (source.GetGroupParent(i) != currentParent)
288                         {
289                             currentParent = source.GetGroupParent(i);
290                             float currentSize = (source.IsGroupHeader(i)? groupHeaderSize :
291                                                     (source.IsGroupFooter(i)? groupFooterSize: StepCandidate));
292                             currentGroup = new GroupInfo()
293                             {
294                                 GroupParent = currentParent,
295                                 //hasHeader = true,
296                                 //hasFooter = false,
297                                 StartIndex = i,
298                                 Count = 1,
299                                 GroupSize = currentSize,
300                                 GroupPosition = Current
301                             };
302                             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
303                                 currentGroup.ItemPosition.Add(0);
304                             groups.Add(currentGroup);
305                             if (source.IsGroupHeader(i)) Current += currentSize;
306                         }
307                         //optional
308                         else if (source.IsGroupFooter(i))
309                         {
310                             //currentGroup.hasFooter = true;
311                             currentGroup.Count++;
312                             currentGroup.GroupSize += groupFooterSize;
313                             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
314                                 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
315                             Current += groupFooterSize;
316                         }
317                         else
318                         {
319                             currentGroup.Count++;
320                             currentGroup.GroupSize += StepCandidate;
321                             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
322                                 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
323                             Current += StepCandidate;
324                         }
325                     }
326                 }
327                 else
328                 {
329                     if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
330                         ItemPosition.Add(Current);
331
332                     if (i == 0 && hasHeader) Current += headerSize;
333                     else if (i == count - 1 && hasFooter) Current += footerSize;
334                     else Current += StepCandidate;
335                 }
336             }
337
338             ScrollContentSize = Current + (IsHorizontal? Padding.End : Padding.Bottom);
339             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
340             else colView.ContentContainer.SizeHeight = ScrollContentSize;
341
342             base.Initialize(view);
343             //Console.WriteLine("[NUI] Init Done, StepCnadidate{0}, Scroll{1}", StepCandidate, ScrollContentSize);
344         }
345
346         /// <summary>
347         /// This is called to find out where items are lain out according to current scroll position.
348         /// </summary>
349         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
350         /// <param name="force">boolean force flag to layouting forcely.</param>
351         /// <since_tizen> 9 </since_tizen>
352         public override void RequestLayout(float scrollPosition, bool force = false)
353         {
354             // Layouting is only possible after once it initialized.
355             if (!IsInitialized) return;
356
357             if (requestLayoutTimer != null)
358             {
359                 requestLayoutTimer.Dispose();
360                 requestLayoutTimer = null;
361                 force = true;
362             }
363
364             int LastIndex = colView.InternalItemSource.Count - 1;
365
366             if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
367             PrevScrollPosition = Math.Abs(scrollPosition);
368
369             if (ItemSizeChanged >= 0)
370             {
371                 for (int i = ItemSizeChanged; i <= LastIndex; i++)
372                     UpdatePosition(i);
373                 (float updateX, float updateY) = GetItemPosition(LastIndex);
374                 ScrollContentSize = GetItemStepSize(LastIndex) + (IsHorizontal? updateX + Padding.End : updateY + Padding.Bottom);
375             }
376
377             int prevFirstVisible = FirstVisible;
378             int prevLastVisible = LastVisible;
379
380             (float X, float Y) visibleArea = (PrevScrollPosition,
381                 PrevScrollPosition + (IsHorizontal? colView.Size.Width : colView.Size.Height)
382             );
383
384             // 1. Set First/Last Visible Item Index. 
385             (int start, int end) = FindVisibleItems(visibleArea);
386             FirstVisible = start;
387             LastVisible = end;
388
389             // 2. Unrealize invisible items.
390             List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
391             foreach (RecyclerViewItem item in VisibleItems)
392             {
393                 if (item.Index < FirstVisible || item.Index > LastVisible)
394                 {
395                     //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
396                     unrealizedItems.Add(item);
397                     colView.UnrealizeItem(item);
398                 }
399             }
400             VisibleItems.RemoveAll(unrealizedItems.Contains);
401             unrealizedItems.Clear();
402
403             // 3. Realize and placing visible items.
404             for (int i = FirstVisible; i <= LastVisible; i++)
405             {
406                 RecyclerViewItem item = null;
407                 // 4. Get item if visible or realize new.
408                 if (i >= prevFirstVisible && i <= prevLastVisible)
409                 {
410                     item = GetVisibleItem(i);
411                     if (item != null && !force) continue;
412                 }
413
414                 if (item == null)
415                 {
416                     item = colView.RealizeItem(i);
417                     if (item != null) VisibleItems.Add(item);
418                     else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
419                 }
420                 // 5. Placing item.
421                 (float posX, float posY) = GetItemPosition(i);
422                 item.Position = new Position(posX, posY);
423
424                 var size = (IsHorizontal? item.SizeWidth: item.SizeHeight);
425
426                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst)
427                 {
428                     if (item.IsHeader || item.IsFooter || item.isGroupHeader || item.isGroupFooter)
429                     {
430                         if (item.IsHeader) size = headerSize;
431                         else if (item.IsFooter) size = footerSize;
432                         else if (item.isGroupHeader) size = groupHeaderSize;
433                         else if (item.isGroupFooter) size = groupFooterSize;
434                     }
435                     else size = StepCandidate;
436                 }
437                 if (IsHorizontal && item.HeightSpecification == LayoutParamPolicies.MatchParent)
438                 {
439                     item.Size = new Size(size, Container.Size.Height - Padding.Top - Padding.Bottom - item.Margin.Top - item.Margin.Bottom);
440                 }
441                 else if (!IsHorizontal && item.WidthSpecification == LayoutParamPolicies.MatchParent)
442                 {
443                     item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, size);
444                 }
445             }
446             return;
447         }
448
449         /// <summary>
450         /// Clear the current screen and all properties.
451         /// </summary>
452         [EditorBrowsable(EditorBrowsableState.Never)]
453         public override void Clear()
454         {
455             // Clean Up
456             if (requestLayoutTimer != null)
457             {
458                 requestLayoutTimer.Dispose();
459             }
460             if (groups != null)
461             {
462                  /*
463                 foreach (GroupInfo group in groups)
464                 {
465                     //group.ItemPosition?.Clear();
466                     // if Disposable?
467                     //group.Dispose();
468                 }
469                 */
470                 groups.Clear();
471             }
472             if (ItemPosition != null)
473             {
474                 ItemPosition.Clear();
475             }
476             if (ItemSize != null)
477             {
478                 ItemSize.Clear();
479             }
480             if (headerMargin != null)
481             {
482                 headerMargin.Dispose();
483                 headerMargin = null;
484             }
485             if (footerMargin != null)
486             {
487                 footerMargin.Dispose();
488                 footerMargin = null;
489             }
490             if (groupHeaderMargin != null)
491             {
492                 groupHeaderMargin.Dispose();
493                 groupHeaderMargin = null;
494             }
495             if (groupFooterMargin != null)
496             {
497                 groupFooterMargin.Dispose();
498                 groupFooterMargin = null;
499             }
500
501             base.Clear();
502         }
503
504
505         /// <inheritdoc/>
506         [EditorBrowsable(EditorBrowsableState.Never)]
507         public override void NotifyItemSizeChanged(RecyclerViewItem item)
508         {
509             if (item == null)
510                 throw new ArgumentNullException(nameof(item));
511
512             if (!IsInitialized ||
513                 (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst &&
514                 item.Index != 0) ||
515                 (item.Index < 0))
516                 return;
517
518             float PrevSize, CurrentSize;
519             if (item.Index == (colView.InternalItemSource.Count - 1))
520             {
521                 PrevSize = ScrollContentSize - ItemPosition[item.Index];
522             }
523             else
524             {
525                 PrevSize = ItemPosition[item.Index + 1] - ItemPosition[item.Index];
526             }
527
528             CurrentSize = (IsHorizontal? item.Size.Width : item.Size.Height);
529
530             if (CurrentSize != PrevSize)
531             {
532                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
533                     ItemSize[item.Index] = CurrentSize;
534                 else
535                     StepCandidate = CurrentSize;
536             }
537             if (ItemSizeChanged == -1) ItemSizeChanged = item.Index;
538             else ItemSizeChanged = Math.Min(ItemSizeChanged, item.Index);
539
540             //ScrollContentSize += Diff; UpdateOnce?
541         }
542
543         /// <Inheritdoc/>
544         [EditorBrowsable(EditorBrowsableState.Never)]
545         public override void NotifyItemInserted(IItemSource source, int startIndex)
546         {
547             // Insert Single item.
548             if (source == null) throw new ArgumentNullException(nameof(source));
549
550             if (isSourceEmpty)
551             {
552                 Initialize(colView);
553             }
554
555             // Will be null if not a group.
556             float currentSize = StepCandidate;
557             IGroupableItemSource gSource = source as IGroupableItemSource;
558
559             // Get the first Visible Position to adjust.
560             /*
561             int topInScreenIndex = 0;
562             float offset = 0F;
563             (topInScreenIndex, offset) = FindTopItemInScreen();
564             */
565         
566             // 1. Handle MeasureAll
567             /*
568             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
569             {
570                 //Need To Implement
571             }
572             */
573
574             //2. Handle Group Case.
575             if (isGrouped && gSource != null)
576             {
577                 GroupInfo groupInfo = null;
578                 object groupParent = gSource.GetGroupParent(startIndex);
579                 int parentIndex = gSource.GetPosition(groupParent);
580                 if (gSource.HasHeader) parentIndex--;
581
582                 // Check item is group parent or not
583                 // if group parent, add new gorupinfo
584                 if (gSource.IsHeader(startIndex))
585                 {
586                     // This is childless group.
587                     // create new groupInfo!
588                     groupInfo = new GroupInfo()
589                     {
590                         GroupParent = groupParent,
591                         StartIndex = startIndex,
592                         Count = 1,
593                         GroupSize = groupHeaderSize,
594                     };
595
596                     if (parentIndex >= groups.Count)
597                     {
598                         groupInfo.GroupPosition = ScrollContentSize;
599                         groups.Add(groupInfo);
600                     }
601                     else
602                     {
603                         groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
604                         groups.Insert(parentIndex, groupInfo);
605                     }
606
607                     currentSize = groupHeaderSize;
608                 }
609                 else
610                 {
611                     // If not group parent, add item into the groupinfo.
612                     if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
613                     groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
614                     if (groupInfo == null) throw new Exception("Cannot find group information!");
615                     groupInfo.Count++;
616
617                     if (gSource.IsGroupFooter(startIndex))
618                     {
619                         // It doesn't make sence to adding footer by notify...
620                         // if GroupFooterTemplate is added,
621                         // need to implement on here.
622                     }
623                     else
624                     {
625                         if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
626                         {
627                             float curPos = groupInfo.ItemPosition[startIndex - groupInfo.StartIndex];
628                             groupInfo.ItemPosition.Insert(startIndex - groupInfo.StartIndex, curPos);
629                             for (int i = startIndex - groupInfo.StartIndex; i < groupInfo.Count; i++)
630                             {
631                                 groupInfo.ItemPosition[i] = curPos;
632                                 curPos += GetItemStepSize(parentIndex + i);
633                             }
634                             groupInfo.GroupSize = curPos;
635                         }
636                         else
637                         {
638                             groupInfo.GroupSize += currentSize;
639                         }
640                     }
641                 }
642
643                 if (parentIndex + 1 < groups.Count)
644                 {
645                     for(int i = parentIndex + 1; i < groups.Count; i++)
646                     {
647                         groups[i].GroupPosition += currentSize;
648                         groups[i].StartIndex++;
649                     }
650                 }
651             }
652             else
653             {
654                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
655                 {
656                     // Need to Implements
657                 }
658
659             }
660
661             // 3. Update Scroll Content Size
662             ScrollContentSize += currentSize;
663
664             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
665             else colView.ContentContainer.SizeHeight = ScrollContentSize;
666
667             // 4. Update Visible Items.
668             foreach (RecyclerViewItem item in VisibleItems)
669             {
670                 if (item.Index >= startIndex)
671                 {
672                     item.Index++;
673                 }
674             }
675
676
677             float scrollPosition = PrevScrollPosition;
678
679             /*
680             // Position Adjust
681             // Insertion above Top Visible!
682             if (startIndex <= topInScreenIndex)
683             {
684                 scrollPosition = GetItemPosition(topInScreenIndex);
685                 scrollPosition -= offset;
686
687                 colView.ScrollTo(scrollPosition);
688             }
689             */
690
691             // Update Viewport in delay.
692             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
693             // but currently we do not have any accessor to pre-calculation so instead of this,
694             // using Timer temporarily.
695             DelayedRequestLayout(scrollPosition);
696         }
697
698         /// <Inheritdoc/>
699         [EditorBrowsable(EditorBrowsableState.Never)]
700         public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
701         {
702              // Insert Group
703             if (source == null) throw new ArgumentNullException(nameof(source));
704
705             if (isSourceEmpty)
706             {
707                 Initialize(colView);
708             }
709
710             float currentSize = StepCandidate;
711             // Will be null if not a group.
712             IGroupableItemSource gSource = source as IGroupableItemSource;
713
714             // Get the first Visible Position to adjust.
715             /*
716             int topInScreenIndex = 0;
717             float offset = 0F;
718             (topInScreenIndex, offset) = FindTopItemInScreen();
719             */
720
721             // 1. Handle MeasureAll
722             /*
723             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
724             {
725                 //Need To Implement
726             }
727             */
728
729             // 2. Handle Group Case
730             // Adding ranged items should all same new groups.
731             if (isGrouped && gSource != null)
732             {
733                 GroupInfo groupInfo = null;
734                 object groupParent = gSource.GetGroupParent(startIndex);
735                 int parentIndex = gSource.GetPosition(groupParent);
736                 if (gSource.HasHeader) parentIndex--;
737
738                 // We guess here that range inserted from GroupStartIndex.
739                 int groupStartIndex = startIndex;
740
741                 for (int current = startIndex; current - startIndex < count; current++)
742                 {
743                     // Check item is group parent or not
744                     // if group parent, add new gorupinfo
745                     if (groupStartIndex == current)
746                     {
747                         currentSize = (gSource.IsGroupHeader(current)? groupHeaderSize :
748                                             (gSource.IsGroupFooter(current)? groupFooterSize: currentSize));
749                         //create new groupInfo!
750                         groupInfo = new GroupInfo()
751                         {
752                             GroupParent = groupParent,
753                             StartIndex = current,
754                             Count = 1,
755                             GroupSize = currentSize,
756                         };
757
758                     }
759                     else
760                     {
761                         //if not group parent, add item into the groupinfo.
762                         //groupInfo = GetGroupInfo(groupStartIndex);
763                         if (groupInfo == null) throw new Exception("Cannot find group information!");
764                         groupInfo.Count++;
765
766                         if (gSource.IsGroupFooter(current))
767                         {
768                                 groupInfo.GroupSize += groupFooterSize;
769                         }
770                         else
771                         {
772                             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
773                             {
774                                //Need To Implement
775                                /*
776                                 float curPos = groupInfo.ItemPosition[current - groupStartIndex];
777                                 groupInfo.ItemPosition.Insert(current - groupStartIndex, curPos);
778                                 for (int i = current - groupStartIndex; i < groupInfo.Count; i++)
779                                 {
780                                     groupInfo.ItemPosition[i] = curPos;
781                                     curPos += GetItemSize(parentIndex + i);
782                                 }
783                                 groupInfo.GroupSize = curPos;
784                                 */
785                             }
786                             else
787                             {
788                                 groupInfo.GroupSize += StepCandidate;
789                             }
790                         }
791                     }
792                 }
793
794                 if (parentIndex >= groups.Count)
795                 {
796                     groupInfo.GroupPosition = ScrollContentSize;
797                     groups.Add(groupInfo);
798                 }
799                 else
800                 {
801                     groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
802                     groups.Insert(parentIndex, groupInfo);
803                 }
804
805                 // Update other below group's position
806                 if (parentIndex + 1 < groups.Count)
807                 {
808                     for(int i = parentIndex + 1; i < groups.Count; i++)
809                     {
810                         groups[i].GroupPosition += groupInfo.GroupSize;
811                         groups[i].StartIndex += count;
812                     }
813                 }
814
815                 ScrollContentSize += groupInfo.GroupSize;
816             }
817             else
818             {
819                 Tizen.Log.Error("NUI", "Not support insert ungrouped range items currently!");
820             }
821
822             // 3. Update Scroll Content Size
823             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
824             else colView.ContentContainer.SizeHeight = ScrollContentSize;
825
826             // 4. Update Visible Items.
827             foreach (RecyclerViewItem item in VisibleItems)
828             {
829                 if (item.Index >= startIndex)
830                 {
831                     item.Index += count;
832                 }
833             }
834
835             // Position Adjust
836             float scrollPosition = PrevScrollPosition;
837             /*
838             // Insertion above Top Visible!
839             if (startIndex + count <= topInScreenIndex)
840             {
841                 scrollPosition = GetItemPosition(topInScreenIndex);
842                 scrollPosition -= offset;
843
844                 colView.ScrollTo(scrollPosition);
845             }
846             */
847
848             // Update Viewport in delay.
849             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
850             // but currently we do not have any accessor to pre-calculation so instead of this,
851             // using Timer temporarily.
852             DelayedRequestLayout(scrollPosition);
853         }
854
855         /// <Inheritdoc/>
856         [EditorBrowsable(EditorBrowsableState.Never)]
857         public override void NotifyItemRemoved(IItemSource source, int startIndex)
858         {
859             // Remove Single
860             if (source == null) throw new ArgumentNullException(nameof(source));
861
862             // Will be null if not a group.
863             float currentSize = StepCandidate;
864             IGroupableItemSource gSource = source as IGroupableItemSource;
865
866             // Get the first Visible Position to adjust.
867             /*
868             int topInScreenIndex = 0;
869             float offset = 0F;
870             (topInScreenIndex, offset) = FindTopItemInScreen();
871             */
872
873             // 1. Handle MeasureAll
874             /*
875             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
876             {
877                 //Need To Implement
878             }
879             */
880
881             // 2. Handle Group Case
882             if (isGrouped && gSource != null)
883             {
884                 int parentIndex = 0;
885                 GroupInfo groupInfo = null;
886                 foreach(GroupInfo cur in groups)
887                 {
888                     if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
889                     {
890                         groupInfo = cur;
891                         break;
892                     }
893                     parentIndex++;
894                 }
895                 if (groupInfo == null) throw new Exception("Cannot find group information!");
896                 // Check item is group parent or not
897                 // if group parent, add new gorupinfo
898                 if (groupInfo.StartIndex == startIndex)
899                 {
900                     // This is empty group!
901                     // check group is empty.
902                     if (groupInfo.Count != 1)
903                     {
904                         throw new Exception("Cannot remove group parent");
905                     }
906                     currentSize = groupInfo.GroupSize;
907
908                     // Remove Group
909                     // groupInfo.Dispose();
910                     groups.Remove(groupInfo);
911                 }
912                 else
913                 {
914                     groupInfo.Count--;
915
916                     if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
917                     {
918                         //Need to Implement this.
919                     }
920                     else
921                     {
922                         groupInfo.GroupSize -= currentSize;
923                     }
924                 }
925
926                 for (int i = parentIndex + 1; i < groups.Count; i++)
927                 {
928                     groups[i].GroupPosition -= currentSize;
929                     groups[i].StartIndex--;
930                 }
931             }
932             else
933             {
934                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
935                 {
936                     // Need to Implements
937                 }
938                 // else Nothing to Do
939             }
940
941             ScrollContentSize -= currentSize;
942
943             // 3. Update Scroll Content Size
944             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
945             else colView.ContentContainer.SizeHeight = ScrollContentSize;
946
947             // 4. Update Visible Items.
948             RecyclerViewItem targetItem = null;
949             foreach (RecyclerViewItem item in VisibleItems)
950             {
951                 if (item.Index == startIndex)
952                 {
953                     targetItem = item;
954                     colView.UnrealizeItem(item);
955                 }
956                 else if (item.Index > startIndex)
957                 {
958                     item.Index--;
959                 }
960             }
961             VisibleItems.Remove(targetItem);
962
963             // Position Adjust
964             float scrollPosition = PrevScrollPosition;
965             /*
966             // Insertion above Top Visible!
967             if (startIndex <= topInScreenIndex)
968             {
969                 scrollPosition = GetItemPosition(topInScreenIndex);
970                 scrollPosition -= offset;
971
972                 colView.ScrollTo(scrollPosition);
973             }
974             */
975
976             // Update Viewport in delay.
977             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
978             // but currently we do not have any accessor to pre-calculation so instead of this,
979             // using Timer temporarily.
980             DelayedRequestLayout(scrollPosition);
981         }
982
983         /// <Inheritdoc/>
984         [EditorBrowsable(EditorBrowsableState.Never)]
985         public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
986         {
987             // Remove Group
988             if (source == null) throw new ArgumentNullException(nameof(source));
989
990             // Will be null if not a group.
991             float currentSize = StepCandidate;
992             IGroupableItemSource gSource = source as IGroupableItemSource;
993
994             // Get the first Visible Position to adjust.
995             /*
996             int topInScreenIndex = 0;
997             float offset = 0F;
998             (topInScreenIndex, offset) = FindTopItemInScreen();
999             */
1000
1001             // 1. Handle MeasureAll
1002             /*
1003             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1004             {
1005                 //Need To Implement
1006             }
1007             */
1008
1009             // 2. Handle Group Case
1010             if (isGrouped && gSource != null)
1011             {
1012                 int parentIndex = 0;
1013                 GroupInfo groupInfo = null;
1014                 foreach(GroupInfo cur in groups)
1015                 {
1016                     if ((cur.StartIndex == startIndex) && (cur.Count == count))
1017                     {
1018                         groupInfo = cur;
1019                         break;
1020                     }
1021                     parentIndex++;
1022                 }
1023                 if (groupInfo == null) throw new Exception("Cannot find group information!");
1024                 // Check item is group parent or not
1025                 // if group parent, add new gorupinfo
1026                 currentSize = groupInfo.GroupSize;
1027                 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1028                 {
1029                     // Update ItemSize and ItemPosition
1030                 }
1031                 // Remove Group
1032                 // groupInfo.Dispose();
1033                 groups.Remove(groupInfo);
1034
1035                 for (int i = parentIndex; i < groups.Count; i++)
1036                 {
1037                     groups[i].GroupPosition -= currentSize;
1038                     groups[i].StartIndex -= count;
1039                 }
1040             }
1041             else
1042             {
1043                 //It must group case! throw exception!
1044                 Tizen.Log.Error("NUI", "Not support remove ungrouped range items currently!");
1045             }
1046
1047             ScrollContentSize -= currentSize;
1048
1049             // 3. Update Scroll Content Size
1050             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1051             else colView.ContentContainer.SizeHeight = ScrollContentSize;
1052
1053             // 4. Update Visible Items.
1054             List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
1055             foreach (RecyclerViewItem item in VisibleItems)
1056             {
1057                 if ((item.Index >= startIndex)
1058                     && (item.Index < startIndex + count))
1059                 {
1060                     unrealizedItems.Add(item);
1061                     colView.UnrealizeItem(item);
1062                 }
1063                 else if (item.Index >= startIndex + count)
1064                 {
1065                     item.Index -= count;
1066                 }
1067             }
1068             VisibleItems.RemoveAll(unrealizedItems.Contains);
1069             unrealizedItems.Clear();
1070
1071             // Position Adjust
1072             float scrollPosition = PrevScrollPosition;
1073             /*
1074             // Insertion above Top Visible!
1075             if (startIndex <= topInScreenIndex)
1076             {
1077                 scrollPosition = GetItemPosition(topInScreenIndex);
1078                 scrollPosition -= offset;
1079
1080                 colView.ScrollTo(scrollPosition);
1081             }
1082             */
1083
1084             // Update Viewport in delay.
1085             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1086             // but currently we do not have any accessor to pre-calculation so instead of this,
1087             // using Timer temporarily.
1088             DelayedRequestLayout(scrollPosition);
1089         }
1090
1091         /// <Inheritdoc/>
1092         [EditorBrowsable(EditorBrowsableState.Never)]
1093         public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
1094         {
1095             // Reorder Single
1096             if (source == null) throw new ArgumentNullException(nameof(source));
1097
1098             // Will be null if not a group.
1099             float currentSize = StepCandidate;
1100             int diff = toPosition - fromPosition;
1101
1102             // Get the first Visible Position to adjust.
1103             /*
1104             int topInScreenIndex = 0;
1105             float offset = 0F;
1106             (topInScreenIndex, offset) = FindTopItemInScreen();
1107             */
1108
1109             // 1. Handle MeasureAll
1110             /*
1111             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1112             {
1113                 //Need To Implement
1114             }
1115             */
1116             
1117             // Move can only happen in it's own groups.
1118             // so there will be no changes in position, startIndex in ohter groups.
1119             // check visible item and update indexs.
1120             int startIndex = ( diff > 0 ? fromPosition: toPosition);
1121             int endIndex = (diff > 0 ? toPosition: fromPosition);
1122
1123             if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1124             {
1125                 foreach (RecyclerViewItem item in VisibleItems)
1126                 {
1127                     if ((item.Index >= startIndex)
1128                         && (item.Index <= endIndex))
1129                     {
1130                         if (item.Index == fromPosition) item.Index = toPosition;
1131                         else
1132                         {
1133                             if (diff > 0) item.Index--;
1134                             else item.Index++;
1135                         }
1136                     }
1137                 }
1138             }
1139
1140             if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1141             else colView.ContentContainer.SizeHeight = ScrollContentSize;
1142
1143             // Position Adjust
1144             float scrollPosition = PrevScrollPosition;
1145             /*
1146             // Insertion above Top Visible!
1147             if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1148                 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1149             {
1150                 scrollPosition = GetItemPosition(topInScreenIndex);
1151                 scrollPosition -= offset;
1152
1153                 colView.ScrollTo(scrollPosition);
1154             }
1155             */
1156
1157             // Update Viewport in delay.
1158             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1159             // but currently we do not have any accessor to pre-calculation so instead of this,
1160             // using Timer temporarily.
1161             DelayedRequestLayout(scrollPosition);
1162         }
1163
1164         /// <Inheritdoc/>
1165         [EditorBrowsable(EditorBrowsableState.Never)]
1166         public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
1167         {
1168             // Reorder Groups
1169             if (source == null) throw new ArgumentNullException(nameof(source));
1170
1171             // Will be null if not a group.
1172             float currentSize = StepCandidate;
1173             int diff = toPosition - fromPosition;
1174
1175             int startIndex = ( diff > 0 ? fromPosition: toPosition);
1176             int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
1177
1178             // 2. Handle Group Case
1179             if (isGrouped)
1180             {
1181                 int fromParentIndex = 0;
1182                 int toParentIndex = 0;
1183                 bool findFrom = false;
1184                 bool findTo = false;
1185                 GroupInfo fromGroup = null;
1186                 GroupInfo toGroup = null;
1187
1188                 foreach(GroupInfo cur in groups)
1189                 {
1190                     if ((cur.StartIndex == fromPosition) && (cur.Count == count))
1191                     {
1192                         fromGroup = cur;
1193                         findFrom = true;
1194                         if (findFrom && findTo) break;
1195                     }
1196                     else if (cur.StartIndex == toPosition)
1197                     {
1198                         toGroup = cur;
1199                         findTo = true;
1200                         if (findFrom && findTo) break;
1201                     }
1202                     if (!findFrom) fromParentIndex++;
1203                     if (!findTo) toParentIndex++;
1204                 }
1205                 if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
1206
1207                 fromGroup.StartIndex = toGroup.StartIndex;
1208                 fromGroup.GroupPosition = toGroup.GroupPosition;
1209
1210                 endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
1211
1212                 groups.Remove(fromGroup);
1213                 groups.Insert(toParentIndex, fromGroup);
1214
1215                 int startGroup = (diff > 0? fromParentIndex: toParentIndex);
1216                 int endGroup =  (diff > 0? toParentIndex: fromParentIndex);
1217                 
1218                 for (int i = startGroup; i <= endGroup; i++)
1219                 {
1220                     if (i == toParentIndex) continue;
1221                     float prevPos = groups[i].GroupPosition;
1222                     int prevIdx = groups[i].StartIndex;
1223                     groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
1224                     groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
1225                 }
1226             }
1227             else
1228             {
1229                 //It must group case! throw exception!
1230                 Tizen.Log.Error("NUI", "Not support move ungrouped range items currently!");
1231             }
1232
1233             // Move can only happen in it's own groups.
1234             // so there will be no changes in position, startIndex in ohter groups.
1235             // check visible item and update indexs.
1236             if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1237             {
1238                 foreach (RecyclerViewItem item in VisibleItems)
1239                 {
1240                     if ((item.Index >= startIndex)
1241                         && (item.Index <= endIndex))
1242                     {
1243                         if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
1244                         {
1245                             item.Index = fromPosition - item.Index + toPosition;
1246                         }
1247                         else
1248                         {
1249                             if (diff > 0) item.Index -= count;
1250                             else item.Index += count;
1251                         }
1252                     }
1253                 }
1254             }
1255
1256             // Position Adjust
1257             float scrollPosition = PrevScrollPosition;
1258             /*
1259             // Insertion above Top Visible!
1260             if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1261                 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1262             {
1263                 scrollPosition = GetItemPosition(topInScreenIndex);
1264                 scrollPosition -= offset;
1265
1266                 colView.ScrollTo(scrollPosition);
1267             }
1268             */
1269
1270             // Update Viewport in delay.
1271             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1272             // but currently we do not have any accessor to pre-calculation so instead of this,
1273             // using Timer temporarily.
1274             DelayedRequestLayout(scrollPosition);
1275         }
1276
1277         /// <Inheritdoc/>
1278         [EditorBrowsable(EditorBrowsableState.Never)]
1279         public override void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
1280         {
1281             // Reorder Group
1282             if (source == null) throw new ArgumentNullException(nameof(source));
1283             IGroupableItemSource gSource = source as IGroupableItemSource;
1284             if (gSource == null)throw new Exception("Source is not group!");
1285
1286             // Get the first Visible Position to adjust.
1287             /*
1288             int topInScreenIndex = 0;
1289             float offset = 0F;
1290             (topInScreenIndex, offset) = FindTopItemInScreen();
1291             */
1292
1293
1294             // Unrealize, initialized all items in the Range
1295             // and receate all.
1296
1297             // Update Viewport in delay.
1298             // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1299             // but currently we do not have any accessor to pre-calculation so instead of this,
1300             // using Timer temporarily.
1301             DelayedRequestLayout(PrevScrollPosition);
1302         }
1303
1304         /// <Inheritdoc/>
1305         [EditorBrowsable(EditorBrowsableState.Never)]
1306         public override float CalculateLayoutOrientationSize()
1307         {
1308             //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
1309             return ScrollContentSize;
1310         }
1311
1312         /// <Inheritdoc/>
1313         [EditorBrowsable(EditorBrowsableState.Never)]
1314         public override float CalculateCandidateScrollPosition(float scrollPosition)
1315         {
1316             //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
1317             return scrollPosition;
1318         }
1319
1320         /// <Inheritdoc/>
1321         [EditorBrowsable(EditorBrowsableState.Never)]
1322         public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1323         {
1324             if (currentFocusedView == null)
1325                 throw new ArgumentNullException(nameof(currentFocusedView));
1326
1327             View nextFocusedView = null;
1328             int targetSibling = -1;
1329
1330             switch (direction)
1331             {
1332                 case View.FocusDirection.Left:
1333                     {
1334                         targetSibling = IsHorizontal? currentFocusedView.SiblingOrder - 1 : targetSibling;
1335                         break;
1336                     }
1337                 case View.FocusDirection.Right:
1338                     {
1339                         targetSibling = IsHorizontal? currentFocusedView.SiblingOrder + 1 : targetSibling;
1340                         break;
1341                     }
1342                 case View.FocusDirection.Up:
1343                     {
1344                         targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder - 1;
1345                         break;
1346                     }
1347                 case View.FocusDirection.Down:
1348                     {
1349                         targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder + 1;
1350                         break;
1351                     }
1352             }
1353
1354             if (targetSibling > -1 && targetSibling < Container.Children.Count)
1355             {
1356                 RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
1357                 if (candidate != null && candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
1358                 {
1359                     nextFocusedView = candidate;
1360                 }
1361             }
1362
1363             return nextFocusedView;
1364         }
1365
1366         /// <inheritdoc/>
1367         [EditorBrowsable(EditorBrowsableState.Never)]
1368         protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
1369         {
1370             int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter? 1 : 0);
1371             int adds = 5;
1372             int skipGroup = -2;
1373             (int start, int end) found = (0, 0);
1374
1375             // 1. Find the start index.
1376             // Header is Showing
1377             if (hasHeader && visibleArea.X <= headerSize + (IsHorizontal? Padding.Start: Padding.Top))
1378             {
1379                 found.start = 0;
1380             }
1381             else
1382             {
1383                 if (isGrouped)
1384                 {
1385                     bool failed = true;
1386                     foreach (GroupInfo gInfo in groups)
1387                     {
1388                         skipGroup++;
1389                         // in the Group
1390                         if (gInfo.GroupPosition <= visibleArea.X &&
1391                             gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
1392                         {
1393                             for (int i = 0; i < gInfo.Count; i++)
1394                             {
1395                                 // Reach last index of group.
1396                                 if (i == (gInfo.Count - 1))
1397                                 {
1398                                     found.start = gInfo.StartIndex + i - adds;
1399                                     failed = false;
1400                                     break;
1401
1402                                 }
1403                                 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.X &&
1404                                         GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.X)
1405                                 {
1406                                     found.start = gInfo.StartIndex + i - adds;
1407                                     failed = false;
1408                                     break;
1409                                 }
1410                             }
1411                         }
1412                     }
1413                     //footer only shows?
1414                     if (failed)
1415                     {
1416                         found.start = MaxIndex;
1417                     }
1418                 }
1419                 else
1420                 {
1421                     float visibleAreaX = visibleArea.X - (hasHeader? headerSize : 0);
1422                     found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / StepCandidate)) - adds);
1423                 }
1424
1425                 if (found.start < 0) found.start = 0;
1426             }
1427
1428             if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
1429             {
1430                 found.end = MaxIndex + 1;
1431             }
1432             else
1433             {
1434                 if (isGrouped)
1435                 {
1436                     bool failed = true;
1437                     // can it be start from founded group...?
1438                     //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
1439                     foreach (GroupInfo gInfo in groups)
1440                     {
1441                         // in the Group
1442                         if (gInfo.GroupPosition <= visibleArea.Y &&
1443                             gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
1444                         {
1445                             for (int i = 0; i < gInfo.Count; i++)
1446                             {
1447                                 if (i == (gInfo.Count - 1))
1448                                 {
1449                                     //Should be groupFooter!
1450                                     found.end = gInfo.StartIndex + i + adds;
1451                                     failed = false;
1452                                     break;
1453
1454                                 }
1455                                 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.Y &&
1456                                         GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.Y)
1457                                 {
1458                                     found.end = gInfo.StartIndex + i + adds;
1459                                     failed = false;
1460                                     break;
1461                                 }
1462                             }
1463                         }
1464                     }
1465                     if (failed) found.end = MaxIndex;
1466                 }
1467                 else
1468                 {
1469                     float visibleAreaY = visibleArea.Y - (hasHeader? headerSize : 0);
1470                     found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / StepCandidate)) + adds);
1471                     if (hasHeader) found.end += 1;
1472                 }
1473                 if (found.end > (MaxIndex)) found.end = MaxIndex;
1474             }
1475             return found;
1476         }
1477
1478         // Item position excluding margins.
1479         internal override (float X, float Y) GetItemPosition(int index)
1480         {
1481             int spaceStartX = Padding.Start;
1482             int spaceStartY = Padding.Top;
1483             if (colView.InternalItemSource.IsHeader(index))
1484             {
1485                 return (spaceStartX + headerMargin.Start, spaceStartY + headerMargin.Top);
1486             }
1487             else if (colView.InternalItemSource.IsFooter(index))
1488             {
1489                 return ((IsHorizontal? ScrollContentSize - footerSize - Padding.End + footerMargin.Start : spaceStartX + footerMargin.Start),
1490                         (IsHorizontal? spaceStartY + footerMargin.Top : ScrollContentSize - footerSize - Padding.Bottom + footerMargin.Top));
1491             }
1492             else if (isGrouped)
1493             {
1494                 GroupInfo gInfo = GetGroupInfo(index);
1495                 if (gInfo == null)
1496                 {
1497                     Tizen.Log.Error("NUI", "GroupInfo failed to get in GetItemPosition()!");
1498                     return (0, 0);
1499                 }
1500                 float current = GetGroupPosition(gInfo, index);
1501                 Extents itemMargin = CandidateMargin;
1502
1503                 if (colView.InternalItemSource.IsGroupHeader(index))
1504                 {
1505                     itemMargin = groupHeaderMargin;
1506                 }
1507                 else if (colView.InternalItemSource.IsGroupFooter(index))
1508                 {
1509                     itemMargin = groupFooterMargin;
1510                 }
1511                 return ((IsHorizontal?
1512                         itemMargin.Start + GetGroupPosition(gInfo, index):
1513                         spaceStartX + itemMargin.Start),
1514                         (IsHorizontal?
1515                         spaceStartY + itemMargin.Top:
1516                         itemMargin.Top + GetGroupPosition(gInfo, index)));
1517             }
1518             else if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1519             {
1520                 //FIXME : CandidateMargin need to be actual itemMargin
1521                 return ((IsHorizontal? ItemPosition[index] + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1522                         (IsHorizontal? spaceStartY + CandidateMargin.Top : ItemPosition[index] + CandidateMargin.Top));
1523             }
1524             else
1525             {
1526                 int adjustIndex = index - (hasHeader ? 1 : 0);
1527                 float current = (IsHorizontal ? spaceStartX : spaceStartY) + (hasHeader? headerSize : 0) + adjustIndex * StepCandidate;
1528                 //FIXME : CandidateMargin need to be actual itemMargin
1529                 return ((IsHorizontal? current + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1530                         (IsHorizontal? spaceStartY + CandidateMargin.Top : current + CandidateMargin.Top));
1531             }
1532         }
1533
1534         // Item size excluding margins. this size is approximated size.
1535         internal override (float Width, float Height) GetItemSize(int index)
1536         {
1537             if (colView.InternalItemSource.IsHeader(index))
1538             {
1539                 return ((IsHorizontal? (int)headerSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1540                         - headerMargin.Start - headerMargin.End,
1541                         (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)headerSize)
1542                         - headerMargin.Top - headerMargin.Bottom);
1543             }
1544             else if (colView.InternalItemSource.IsFooter(index))
1545             {
1546                 return ((IsHorizontal? (int)footerSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1547                         - footerMargin.Start - footerMargin.End,
1548                         (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)footerSize)
1549                         - footerMargin.Top - footerMargin.Bottom);
1550             }
1551             else if (colView.InternalItemSource.IsGroupHeader(index))
1552             {
1553                 return ((IsHorizontal? (int)groupHeaderSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1554                         - groupHeaderMargin.Start - groupHeaderMargin.End,
1555                         (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)groupHeaderSize)
1556                         - groupHeaderMargin.Top - groupHeaderMargin.Bottom);
1557             }
1558             else if (colView.InternalItemSource.IsGroupFooter(index))
1559             {
1560                 return ((IsHorizontal? (int)groupFooterSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1561                         - groupFooterMargin.Start - groupFooterMargin.End,
1562                         (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)groupFooterSize)
1563                         - groupFooterMargin.Top - groupFooterMargin.Bottom);
1564             }
1565             else
1566             {
1567                 return ((IsHorizontal? (int)StepCandidate : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1568                         - CandidateMargin.Start - CandidateMargin.End,
1569                         (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)StepCandidate)
1570                         - CandidateMargin.Top - CandidateMargin.Bottom);
1571             }            
1572         }
1573
1574         private void DelayedRequestLayout(float scrollPosition , bool force = true)
1575         {
1576             if (requestLayoutTimer != null)
1577             {
1578                 requestLayoutTimer.Dispose();
1579             }
1580
1581             requestLayoutTimer = new Timer(1);
1582             requestLayoutTimer.Interval = 1;
1583             requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
1584             {
1585                 RequestLayout(scrollPosition, force);
1586                 return false;
1587             });
1588         }
1589
1590         /*
1591         private (int, float) FindTopItemInScreen()
1592         {
1593             int index = -1;
1594             float offset = 0.0F, Pos, Size;
1595
1596             foreach(RecyclerViewItem item in VisibleItems)
1597             {
1598                 Pos = IsHorizontal ? item.PositionX : item.PositionY;
1599                 Size = IsHorizontal ? item.SizeWidth : item.SizeHeight;
1600                 if (PrevScrollPosition >= Pos && PrevScrollPosition < Pos + Size)
1601                 {
1602                     index = item.Index;
1603                     offset = Pos - PrevScrollPosition;
1604                     break;
1605                 }
1606             }
1607
1608             return (index, offset);
1609         }
1610         */
1611
1612         private float GetItemStepSize(int index)
1613         {
1614             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1615             {
1616                 return ItemSize[index];
1617             }
1618             else
1619             {
1620                 if (colView.InternalItemSource.IsHeader(index))
1621                     return headerSize;
1622                 else if (colView.InternalItemSource.IsFooter(index))
1623                     return footerSize;
1624                 else if (colView.InternalItemSource.IsGroupHeader(index))
1625                     return groupHeaderSize;
1626                 else if (colView.InternalItemSource.IsGroupFooter(index))
1627                     return groupFooterSize;
1628                 else
1629                     return StepCandidate;
1630             }            
1631         }
1632
1633         private void UpdatePosition(int index)
1634         {
1635             bool IsGroup = (colView.InternalItemSource is IGroupableItemSource);
1636
1637             if (index <= 0) return;
1638             if (index >= colView.InternalItemSource.Count)
1639
1640                 if (IsGroup)
1641                 {
1642                     //IsGroupHeader = (colView.InternalItemSource as IGroupableItemSource).IsGroupHeader(index);
1643                     //IsGroupFooter = (colView.InternalItemSource as IGroupableItemSource).IsGroupFooter(index);
1644                     //Do Something
1645                 }
1646             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1647                 ItemPosition[index] = ItemPosition[index - 1] + GetItemStepSize(index - 1);
1648         }
1649
1650         private RecyclerViewItem GetVisibleItem(int index)
1651         {
1652             foreach (RecyclerViewItem item in VisibleItems)
1653             {
1654                 if (item.Index == index) return item;
1655             }
1656             return null;
1657         }
1658
1659         private GroupInfo GetGroupInfo(int index)
1660         {
1661             if (Visited != null)
1662             {
1663                 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1664                     return Visited;
1665             }
1666             if (hasHeader && index == 0) return null;
1667             foreach (GroupInfo group in groups)
1668             {
1669                 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1670                 {
1671                     Visited = group;
1672                     return group;
1673                 }
1674             }
1675             Visited = null;
1676             return null;
1677         }
1678
1679         private float GetGroupPosition(GroupInfo groupInfo, int index)
1680         {
1681             if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1682                 return groupInfo.GroupPosition + groupInfo.ItemPosition[index - groupInfo.StartIndex];
1683             else
1684             {
1685                 float pos = groupInfo.GroupPosition;
1686                 if (groupInfo.StartIndex == index) return pos;
1687
1688                 pos = pos + groupHeaderSize + StepCandidate * (index - groupInfo.StartIndex - 1);
1689
1690                 return pos;
1691             }
1692         }
1693         /*
1694                 private object GetGroupParent(int index)
1695                 {
1696                     if (Visited != null)
1697                     {
1698                         if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1699                         return Visited.GroupParent;
1700                     }
1701                     if (hasHeader && index == 0) return null;
1702                     foreach (GroupInfo group in groups)
1703                     {
1704                         if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1705                         {
1706                             Visited = group;
1707                             return group.GroupParent;
1708                         }
1709                     }
1710                     Visited = null;
1711                     return null;
1712                 }
1713         */
1714         class GroupInfo
1715         {
1716             public object GroupParent;
1717             public int StartIndex;
1718             public int Count;
1719             public float GroupSize;
1720             public float GroupPosition;
1721             //Items relative position from the GroupPosition. Only use for MeasureAll.
1722             public List<float> ItemPosition = new List<float>();
1723         }
1724     }
1725 }