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