[NUI] updating InternalItemsSource when header/footer is updated (#3117)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / RecyclerView / CollectionView.cs
1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 using System;
17 using System.Linq;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Collections.Specialized;
21 using System.Windows.Input;
22 using System.ComponentModel;
23 using Tizen.NUI.BaseComponents;
24 using Tizen.NUI.Binding;
25
26 namespace Tizen.NUI.Components
27 {
28     /// <summary>
29     /// Selectable RecyclerView that presenting a collection of items with variable layouters.
30     /// </summary>
31     /// <since_tizen> 9 </since_tizen>
32     public class CollectionView : RecyclerView
33     {
34         /// <summary>
35         /// Binding Property of selected item in single selection.
36         /// </summary>
37         /// <since_tizen> 9 </since_tizen>
38         public static readonly BindableProperty SelectedItemProperty =
39             BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(CollectionView), null,
40                 propertyChanged: (bindable, oldValue, newValue) =>
41                 {
42                     var colView = (CollectionView)bindable;
43                     oldValue = colView.selectedItem;
44                     colView.selectedItem = newValue;
45                     var args = new SelectionChangedEventArgs(oldValue, newValue);
46
47                     foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
48                     {
49                         if (item.BindingContext == null) continue;
50                         if (item.BindingContext == oldValue) item.IsSelected = false;
51                         else if (item.BindingContext == newValue) item.IsSelected = true;
52                     }
53
54                     SelectionPropertyChanged(colView, args);
55                 },
56                 defaultValueCreator: (bindable) =>
57                 {
58                     var colView = (CollectionView)bindable;
59                     return colView.selectedItem;
60                 });
61
62         /// <summary>
63         /// Binding Property of selected items list in multiple selection.
64         /// </summary>
65         /// <since_tizen> 9 </since_tizen>
66         public static readonly BindableProperty SelectedItemsProperty =
67             BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(CollectionView), null,
68                 propertyChanged: (bindable, oldValue, newValue) =>
69                 {
70                     var colView = (CollectionView)bindable;
71                     var oldSelection = colView.selectedItems ?? selectEmpty;
72                     //FIXME : CoerceSelectedItems calls only isCreatedByXaml
73                     var newSelection = (SelectionList)CoerceSelectedItems(colView, newValue);
74                     colView.selectedItems = newSelection;
75                     colView.SelectedItemsPropertyChanged(oldSelection, newSelection);
76                 },
77                 defaultValueCreator: (bindable) =>
78                 {
79                     var colView = (CollectionView)bindable;
80                     colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
81                     return colView.selectedItems;
82                 });
83
84         /// <summary>
85         /// Binding Property of selected items list in multiple selection.
86         /// </summary>
87         /// <since_tizen> 9 </since_tizen>
88         public static readonly BindableProperty SelectionModeProperty =
89             BindableProperty.Create(nameof(SelectionMode), typeof(ItemSelectionMode), typeof(CollectionView), ItemSelectionMode.None,
90                 propertyChanged: (bindable, oldValue, newValue) =>
91                 {
92                     var colView = (CollectionView)bindable;
93                     oldValue = colView.selectionMode;
94                     colView.selectionMode = (ItemSelectionMode)newValue;
95                     SelectionModePropertyChanged(colView, oldValue, newValue);
96                 },
97                 defaultValueCreator: (bindable) =>
98                 {
99                     var colView = (CollectionView)bindable;
100                     return colView.selectionMode;
101                 });
102
103
104         private static readonly IList<object> selectEmpty = new List<object>(0);
105         private DataTemplate itemTemplate = null;
106         private IEnumerable itemsSource = null;
107         private ItemsLayouter itemsLayouter = null;
108         private DataTemplate groupHeaderTemplate;
109         private DataTemplate groupFooterTemplate;
110         private bool isGrouped;
111         private bool wasRelayouted = false;
112         private bool needInitalizeLayouter = false;
113         private object selectedItem;
114         private SelectionList selectedItems;
115         private bool suppressSelectionChangeNotification;
116         private ItemSelectionMode selectionMode = ItemSelectionMode.None;
117         private RecyclerViewItem header;
118         private RecyclerViewItem footer;
119         private View focusedView;
120         private int prevFocusedDataIndex = 0;
121         private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
122         private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
123         private bool delayedScrollTo;
124         private (float position, bool anim) delayedScrollToParam;
125
126         private bool delayedIndexScrollTo;
127         private (int index, bool anim, ItemScrollTo scrollTo) delayedIndexScrollToParam;
128
129         /// <summary>
130         /// Base constructor.
131         /// </summary>
132         /// <since_tizen> 9 </since_tizen>
133         public CollectionView() : base()
134         {
135             FocusGroup = true;
136             SetKeyboardNavigationSupport(true);
137         }
138
139         /// <summary>
140         /// Base constructor with ItemsSource
141         /// </summary>
142         /// <param name="itemsSource">item's data source</param>
143         /// <since_tizen> 9 </since_tizen>
144         public CollectionView(IEnumerable itemsSource) : this()
145         {
146             ItemsSource = itemsSource;
147         }
148
149         /// <summary>
150         /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
151         /// </summary>
152         /// <param name="itemsSource">item's data source</param>
153         /// <param name="layouter">item's layout manager</param>
154         /// <param name="template">item's view template with data bindings</param>
155         [EditorBrowsable(EditorBrowsableState.Never)]
156         public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
157         {
158             ItemsSource = itemsSource;
159             ItemTemplate = template;
160             ItemsLayouter = layouter;
161         }
162
163         /// <summary>
164         /// Event of Selection changed.
165         /// previous selection list and current selection will be provided.
166         /// </summary>
167         /// <since_tizen> 9 </since_tizen>
168         public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
169
170         /// <summary>
171         /// Align item in the viewport when ScrollTo() calls.
172         /// </summary>
173         /// <since_tizen> 9 </since_tizen>
174         public enum ItemScrollTo
175         {
176             /// <summary>
177             /// Scroll to show item in nearest viewport on scroll direction.
178             /// item is above the scroll viewport, item will be came into front,
179             /// item is under the scroll viewport, item will be came into end,
180             /// item is in the scroll viewport, no scroll.
181             /// </summary>
182             /// <since_tizen> 9 </since_tizen>
183             Nearest,
184             /// <summary>
185             /// Scroll to show item in start of the viewport.
186             /// </summary>
187             /// <since_tizen> 9 </since_tizen>
188             Start,
189             /// <summary>
190             /// Scroll to show item in center of the viewport.
191             /// </summary>
192             /// <since_tizen> 9 </since_tizen>
193             Center,
194             /// <summary>
195             /// Scroll to show item in end of the viewport.
196             /// </summary>
197             /// <since_tizen> 9 </since_tizen>
198             End,
199         }
200
201         /// <summary>
202         /// Item's source data in IEnumerable.
203         /// </summary>
204         /// <since_tizen> 9 </since_tizen>
205         public override IEnumerable ItemsSource
206         {
207             get
208             {
209                 return itemsSource;
210             }
211             set
212             {
213                 if (itemsSource != null)
214                 {
215                     // Clearing old data!
216                     if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged)
217                     {
218                         prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged;
219                     }
220                     if (selectedItem != null) selectedItem = null;
221                     selectedItems?.Clear();
222                 }
223
224                 itemsSource = value;
225                 if (value == null)
226                 {
227                     InternalItemSource?.Dispose();
228                     InternalItemSource = null;
229                     itemsLayouter?.Clear();
230                     ClearCache();
231                     return;
232                 }
233                 if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged)
234                 {
235                     newNotifyCollectionChanged.CollectionChanged += CollectionChanged;
236                 }
237
238                 InternalItemSource?.Dispose();
239                 InternalItemSource = ItemsSourceFactory.Create(this);
240
241                 if (itemsLayouter == null) return;
242
243                 needInitalizeLayouter = true;
244                 Init();
245             }
246         }
247
248         /// <summary>
249         /// DataTemplate for items.
250         /// Create visual contents and binding properties.
251         /// return object type is restricted RecyclerViewItem.
252         /// <seealso cref="Tizen.NUI.Binding.DataTemplate" />
253         /// </summary>
254         /// <since_tizen> 9 </since_tizen>
255         public override DataTemplate ItemTemplate
256         {
257             get
258             {
259                 return itemTemplate;
260             }
261             set
262             {
263                 itemTemplate = value;
264                 if (value == null)
265                 {
266                     return;
267                 }
268
269                 needInitalizeLayouter = true;
270                 Init();
271             }
272         }
273
274         /// <summary>
275         /// Items Layouter.
276         /// Layouting items on the scroll ContentContainer.
277         /// <seealso cref="ItemsLayouter" />
278         /// <seealso cref="LinearLayouter" />
279         /// <seealso cref="GridLayouter" />
280         /// </summary>
281         /// <since_tizen> 9 </since_tizen>
282         public virtual ItemsLayouter ItemsLayouter
283         {
284             get
285             {
286                 return itemsLayouter;
287             }
288             set
289             {
290                 itemsLayouter?.Clear();
291                 ClearCache();
292
293                 itemsLayouter = value;
294                 base.InternalItemsLayouter = itemsLayouter;
295                 if (value == null)
296                 {
297                     needInitalizeLayouter = false;
298                     return;
299                 }
300
301                 needInitalizeLayouter = true;
302
303                 var styleName = "Tizen.NUI.Components." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
304                 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
305                 if (layouterStyle != null)
306                 {
307                     itemsLayouter.Padding = new Extents(layouterStyle.Padding);
308                 }
309                 Init();
310             }
311         }
312
313         /// <summary>
314         /// Scrolling direction to display items layout.
315         /// </summary>
316         /// <since_tizen> 9 </since_tizen>
317         public new Direction ScrollingDirection
318         {
319             get
320             {
321                 return base.ScrollingDirection;
322             }
323             set
324             {
325                 if (base.ScrollingDirection != value)
326                 {
327                     base.ScrollingDirection = value;
328                     needInitalizeLayouter = true;
329                     Init();
330                 }
331             }
332         }
333
334         /// <summary>
335         /// Selected item in single selection.
336         /// </summary>
337         /// <since_tizen> 9 </since_tizen>
338         public object SelectedItem
339         {
340             get => GetValue(SelectedItemProperty);
341             set => SetValue(SelectedItemProperty, value);
342         }
343
344         /// <summary>
345         /// Selected items list in multiple selection.
346         /// </summary>
347         /// <since_tizen> 9 </since_tizen>
348         public IList<object> SelectedItems
349         {
350             get => (IList<object>)GetValue(SelectedItemsProperty);
351             // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
352         }
353
354         /// <summary>
355         /// Selection mode to handle items selection. See ItemSelectionMode for details.
356         /// </summary>
357         /// <since_tizen> 9 </since_tizen>
358         public ItemSelectionMode SelectionMode
359         {
360             get => (ItemSelectionMode)GetValue(SelectionModeProperty);
361             set => SetValue(SelectionModeProperty, value);
362         }
363
364         /// <summary>
365         /// Command of selection changed.
366         /// </summary>
367         [EditorBrowsable(EditorBrowsableState.Never)]
368         public ICommand SelectionChangedCommand { set; get; }
369
370         /// <summary>
371         /// Command parameter of selection changed.
372         /// </summary>
373         [EditorBrowsable(EditorBrowsableState.Never)]
374         public object SelectionChangedCommandParameter { set; get; }
375
376         /// <summary>
377         /// Header item placed in top-most position.
378         /// </summary>
379         /// <remarks>Please note that, internal index will be increased by header.</remarks>
380         /// <since_tizen> 9 </since_tizen>
381         public RecyclerViewItem Header
382         {
383             get => header;
384             set
385             {
386                 if (header != null)
387                 {
388                     //ContentContainer.Remove(header);
389                     Utility.Dispose(header);
390                 }
391                 if (value != null)
392                 {
393                     value.Index = 0;
394                     value.ParentItemsView = this;
395                     value.IsHeader = true;
396                     ContentContainer.Add(value);
397                 }
398                 header = value;
399                 if (InternalItemSource != null)
400                 {
401                     InternalItemSource.HasHeader = (value != null);
402                 }
403                 needInitalizeLayouter = true;
404                 Init();
405             }
406         }
407
408         /// <summary>
409         /// Footer item placed in bottom-most position.
410         /// </summary>
411         /// <remarks>Please note that, internal index will be increased by footer.</remarks>
412         /// <since_tizen> 9 </since_tizen>
413         public RecyclerViewItem Footer
414         {
415             get => footer;
416             set
417             {
418                 if (footer != null)
419                 {
420                     //ContentContainer.Remove(footer);
421                     Utility.Dispose(footer);
422                 }
423                 if (value != null)
424                 {
425                     value.Index = InternalItemSource?.Count ?? 0;
426                     value.ParentItemsView = this;
427                     value.IsFooter = true;
428                     ContentContainer.Add(value);
429                 }
430                 footer = value;
431                 if (InternalItemSource != null)
432                 {
433                     InternalItemSource.HasFooter = (value != null);
434                 }
435                 needInitalizeLayouter = true;
436                 Init();
437             }
438         }
439
440         /// <summary>
441         /// Enable groupable view.
442         /// </summary>
443         [EditorBrowsable(EditorBrowsableState.Never)]
444         public bool IsGrouped
445         {
446             get => isGrouped;
447             set
448             {
449                 isGrouped = value;
450                 needInitalizeLayouter = true;
451                 //Need to re-intialize Internal Item Source.
452                 if (InternalItemSource != null)
453                 {
454                     InternalItemSource.Dispose();
455                     InternalItemSource = null;
456                 }
457                 if (ItemsSource != null)
458                     InternalItemSource = ItemsSourceFactory.Create(this);
459                 Init();
460             }
461         }
462
463         /// <summary>
464         ///  DataTemplate of group header.
465         /// </summary>
466         /// <remarks>Please note that, internal index will be increased by group header.
467         /// GroupHeaderTemplate is essential for groupable view.</remarks>        
468         [EditorBrowsable(EditorBrowsableState.Never)]
469         public DataTemplate GroupHeaderTemplate
470         {
471             get
472             {
473                 return groupHeaderTemplate;
474             }
475             set
476             {
477                 groupHeaderTemplate = value;
478                 needInitalizeLayouter = true;
479                 //Need to re-intialize Internal Item Source.
480                 if (InternalItemSource != null)
481                 {
482                     InternalItemSource.Dispose();
483                     InternalItemSource = null;
484                 }
485                 if (ItemsSource != null)
486                     InternalItemSource = ItemsSourceFactory.Create(this);
487                 Init();
488             }
489         }
490
491         /// <summary>
492         /// DataTemplate of group footer. Group feature is not supported yet.
493         /// </summary>
494         /// <remarks>Please note that, internal index will be increased by group footer.</remarks>
495         [EditorBrowsable(EditorBrowsableState.Never)]
496         public DataTemplate GroupFooterTemplate
497         {
498             get
499             {
500                 return groupFooterTemplate;
501             }
502             set
503             {
504                 groupFooterTemplate = value;
505                 needInitalizeLayouter = true;
506                 //Need to re-intialize Internal Item Source.
507                 if (InternalItemSource != null)
508                 {
509                     InternalItemSource.Dispose();
510                     InternalItemSource = null;
511                 }
512                 if (ItemsSource != null)
513                     InternalItemSource = ItemsSourceFactory.Create(this);
514                 Init();
515             }
516         }
517
518         /// <summary>
519         /// Internal encapsulated items data source.
520         /// </summary>
521         internal new IGroupableItemSource InternalItemSource
522         {
523             get
524             {
525                 return (base.InternalItemSource as IGroupableItemSource);
526             }
527             set
528             {
529                 base.InternalItemSource = value;
530             }
531         }
532
533         /// <summary>
534         /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
535         /// </summary>
536         [EditorBrowsable(EditorBrowsableState.Never)]
537         internal ItemSizingStrategy SizingStrategy { get; set; }
538
539         /// <inheritdoc/>
540         /// <since_tizen> 9 </since_tizen>
541         public override void OnRelayout(Vector2 size, RelayoutContainer container)
542         {
543             base.OnRelayout(size, container);
544
545             wasRelayouted = true;
546             if (needInitalizeLayouter) Init();
547         }
548
549         /// <inheritdoc/>
550         [EditorBrowsable(EditorBrowsableState.Never)]
551         public override void NotifyDataSetChanged()
552         {
553             if (selectedItem != null)
554             {
555                 selectedItem = null;
556             }
557             if (selectedItems != null)
558             {
559                 selectedItems.Clear();
560             }
561
562             base.NotifyDataSetChanged();
563         }
564
565         /// <inheritdoc/>
566         [EditorBrowsable(EditorBrowsableState.Never)]
567         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
568         {
569             View nextFocusedView = null;
570
571             if (focusedView == null)
572             {
573                 // If focusedView is null, find child which has previous data index
574                 if (ContentContainer.Children.Count > 0 && InternalItemSource.Count > 0)
575                 {
576                     for (int i = 0; i < ContentContainer.Children.Count; i++)
577                     {
578                         RecyclerViewItem item = Children[i] as RecyclerViewItem;
579                         if (item?.Index == prevFocusedDataIndex)
580                         {
581                             nextFocusedView = item;
582                             break;
583                         }
584                     }
585                 }
586             }
587             else
588             {
589                 // If this is not first focus, request next focus to Layouter
590                 nextFocusedView = ItemsLayouter.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
591             }
592
593             if (nextFocusedView != null)
594             {
595                 // Check next focused view is inside of visible area.
596                 // If it is not, move scroll position to make it visible.
597                 Position scrollPosition = ContentContainer.CurrentPosition;
598                 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
599
600                 float left = nextFocusedView.Position.X;
601                 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
602                 float top = nextFocusedView.Position.Y;
603                 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
604
605                 float visibleRectangleLeft = -scrollPosition.X;
606                 float visibleRectangleRight = -scrollPosition.X + Size.Width;
607                 float visibleRectangleTop = -scrollPosition.Y;
608                 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
609
610                 if (ScrollingDirection == Direction.Horizontal)
611                 {
612                     if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
613                     {
614                         targetPosition = left;
615                     }
616                     else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
617                     {
618                         targetPosition = right - Size.Width;
619                     }
620                 }
621                 else
622                 {
623                     if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
624                     {
625                         targetPosition = top;
626                     }
627                     else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
628                     {
629                         targetPosition = bottom - Size.Height;
630                     }
631                 }
632
633                 focusedView = nextFocusedView;
634                 prevFocusedDataIndex = (nextFocusedView as RecyclerViewItem)?.Index ?? -1;
635
636                 ScrollTo(targetPosition, true);
637             }
638             else
639             {
640                 // If nextView is null, it means that we should move focus to outside of Control.
641                 // Return FocusableView depending on direction.
642                 switch (direction)
643                 {
644                     case View.FocusDirection.Left:
645                         {
646                             nextFocusedView = LeftFocusableView;
647                             break;
648                         }
649                     case View.FocusDirection.Right:
650                         {
651                             nextFocusedView = RightFocusableView;
652                             break;
653                         }
654                     case View.FocusDirection.Up:
655                         {
656                             nextFocusedView = UpFocusableView;
657                             break;
658                         }
659                     case View.FocusDirection.Down:
660                         {
661                             nextFocusedView = DownFocusableView;
662                             break;
663                         }
664                 }
665
666                 if (nextFocusedView != null)
667                 {
668                     focusedView = null;
669                 }
670                 else
671                 {
672                     //If FocusableView doesn't exist, not move focus.
673                     nextFocusedView = focusedView;
674                 }
675             }
676
677             return nextFocusedView;
678         }
679
680         /// <summary>
681         /// Update selected items list in multiple selection.
682         /// </summary>
683         /// <param name="newSelection">updated selection list by user</param>
684         /// <since_tizen> 9 </since_tizen>
685         public void UpdateSelectedItems(IList<object> newSelection)
686         {
687             var oldSelection = new List<object>(SelectedItems);
688
689             suppressSelectionChangeNotification = true;
690
691             SelectedItems.Clear();
692
693             if (newSelection?.Count > 0)
694             {
695                 for (int n = 0; n < newSelection.Count; n++)
696                 {
697                     SelectedItems.Add(newSelection[n]);
698                 }
699             }
700
701             suppressSelectionChangeNotification = false;
702
703             SelectedItemsPropertyChanged(oldSelection, newSelection);
704         }
705
706         /// <summary>
707         /// Scroll to specific position with or without animation.
708         /// </summary>
709         /// <param name="position">Destination.</param>
710         /// <param name="animate">Scroll with or without animation</param>
711         /// <since_tizen> 9 </since_tizen>
712         public new void ScrollTo(float position, bool animate)
713         {
714             if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
715             if ((InternalItemSource == null) || needInitalizeLayouter)
716             {
717                 delayedScrollTo = true;
718                 delayedScrollToParam = (position, animate);
719                 return;
720             }
721
722             base.ScrollTo(position, animate);
723         }
724
725         /// <summary>
726         /// Scrolls to the item at the specified index.
727         /// </summary>
728         /// <param name="index">Index of item.</param>
729         [EditorBrowsable(EditorBrowsableState.Never)]
730         public new void ScrollToIndex(int index)
731         {
732             ScrollTo(index, true, ItemScrollTo.Start);
733         }
734
735         /// <summary>
736         /// Scroll to specific item's aligned position with or without animation.
737         /// </summary>
738         /// <param name="index">Target item index of dataset.</param>
739         /// <param name="animate">Boolean flag of animation.</param>
740         /// <param name="align">Align state of item. See details in <see cref="ItemScrollTo"/>.</param>
741         /// <since_tizen> 9 </since_tizen>
742         public virtual void ScrollTo(int index, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
743         {
744             if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
745             if ((InternalItemSource == null) || needInitalizeLayouter)
746             {
747                 delayedIndexScrollTo = true;
748                 delayedIndexScrollToParam = (index, animate, align);
749                 return;
750             }
751             if (index < 0 || index >= InternalItemSource.Count)
752             {
753                 throw new Exception("index is out of boundary. index should be a value between (0, " + InternalItemSource.Count.ToString() + ").");
754             }
755
756             float scrollPos, curPos, curSize, curItemSize;
757             (float x, float y) = ItemsLayouter.GetItemPosition(index);
758             (float width, float height) = ItemsLayouter.GetItemSize(index);
759             if (ScrollingDirection == Direction.Horizontal)
760             {
761                 scrollPos = x;
762                 curPos = ScrollPosition.X;
763                 curSize = Size.Width;
764                 curItemSize = width;
765             }
766             else
767             {
768                 scrollPos = y;
769                 curPos = ScrollPosition.Y;
770                 curSize = Size.Height;
771                 curItemSize = height;
772             }
773
774             //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
775             switch (align)
776             {
777                 case ItemScrollTo.Start:
778                     //nothing necessary.
779                     break;
780                 case ItemScrollTo.Center:
781                     scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
782                     break;
783                 case ItemScrollTo.End:
784                     scrollPos = scrollPos - curSize + curItemSize;
785                     break;
786                 case ItemScrollTo.Nearest:
787                     if (scrollPos < curPos - curItemSize)
788                     {
789                         // item is placed before the current screen. scrollTo.Top
790                     }
791                     else if (scrollPos >= curPos + curSize + curItemSize)
792                     {
793                         // item is placed after the current screen. scrollTo.End
794                         scrollPos = scrollPos - curSize + curItemSize;
795                     }
796                     else
797                     {
798                         // item is in the scroller. ScrollTo() is ignored.
799                         return;
800                     }
801                     break;
802             }
803
804             //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
805             base.ScrollTo(scrollPos, animate);
806         }
807
808         /// <summary>
809         /// Apply style to CollectionView
810         /// </summary>
811         /// <param name="viewStyle">The style to apply.</param>
812         [EditorBrowsable(EditorBrowsableState.Never)]
813         public override void ApplyStyle(ViewStyle viewStyle)
814         {
815             base.ApplyStyle(viewStyle);
816             if (viewStyle != null)
817             {
818                 //Extension = RecyclerViewItemStyle.CreateExtension();
819             }
820             if (itemsLayouter != null)
821             {
822                 string styleName = "Tizen.NUI.Compoenents." + (itemsLayouter is LinearLayouter? "LinearLayouter" : (itemsLayouter is GridLayouter ? "GridLayouter" : "ItemsLayouter"));
823                 ViewStyle layouterStyle = ThemeManager.GetStyle(styleName);
824                 if (layouterStyle != null)
825                     itemsLayouter.Padding = new Extents(layouterStyle.Padding);
826             }
827         }
828
829         // Realize and Decorate the item.
830         internal override RecyclerViewItem RealizeItem(int index)
831         {
832             RecyclerViewItem item;
833             if (index == 0 && Header != null)
834             {
835                 Header.Show();
836                 return Header;
837             }
838
839             if (index == InternalItemSource.Count - 1 && Footer != null)
840             {
841                 Footer.Show();
842                 return Footer;
843             }
844
845             if (isGrouped)
846             {
847                 var context = InternalItemSource.GetItem(index);
848                 if (InternalItemSource.IsGroupHeader(index))
849                 {
850                     DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
851
852                     RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
853                     if (groupHeader == null)
854                     {
855                         groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
856
857                         groupHeader.Template = templ;
858                         groupHeader.isGroupHeader = true;
859                         groupHeader.isGroupFooter = false;
860                         ContentContainer.Add(groupHeader);
861                     }
862
863                     if (groupHeader != null)
864                     {
865                         groupHeader.ParentItemsView = this;
866                         groupHeader.Index = index;
867                         groupHeader.ParentGroup = context;
868                         groupHeader.BindingContext = context;
869                     }
870                     //group selection?
871                     item = groupHeader;
872                 }
873                 else if (InternalItemSource.IsGroupFooter(index))
874                 {
875                     DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
876
877                     RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
878                     if (groupFooter == null)
879                     {
880                         groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
881
882                         groupFooter.Template = templ;
883                         groupFooter.isGroupHeader = false;
884                         groupFooter.isGroupFooter = true;
885                         ContentContainer.Add(groupFooter);
886                     }
887
888                     if (groupFooter != null)
889                     {
890                         groupFooter.ParentItemsView = this;
891                         groupFooter.Index = index;
892                         groupFooter.ParentGroup = context;
893                         groupFooter.BindingContext = context;
894                     }
895                     //group selection?
896                     item = groupFooter;
897                 }
898                 else
899                 {
900                     item = base.RealizeItem(index);
901                     item.ParentGroup = InternalItemSource.GetGroupParent(index);
902                 }
903             }
904             else
905             {
906                 item = base.RealizeItem(index);
907             }
908
909             switch (SelectionMode)
910             {
911                 case ItemSelectionMode.Single:
912                 case ItemSelectionMode.SingleAlways:
913                     if (item.BindingContext != null && item.BindingContext == SelectedItem)
914                     {
915                         item.IsSelected = true;
916                     }
917                     break;
918
919                 case ItemSelectionMode.Multiple:
920                     if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false))
921                     {
922                         item.IsSelected = true;
923                     }
924                     break;
925                 case ItemSelectionMode.None:
926                     item.IsSelectable = false;
927                     break;
928             }
929             return item;
930         }
931
932         // Unrealize and caching the item.
933         internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
934         {
935             if (item == null) return;
936             if (item == Header)
937             {
938                 item.Hide();
939                 return;
940             }
941             if (item == Footer)
942             {
943                 item.Hide();
944                 return;
945             }
946             if (item.isGroupHeader || item.isGroupFooter)
947             {
948                 item.Index = -1;
949                 item.ParentItemsView = null;
950                 item.BindingContext = null; 
951                 item.IsPressed = false;
952                 item.IsSelected = false;
953                 item.IsEnabled = true;
954                 item.UpdateState();
955                 //item.Relayout -= OnItemRelayout;
956                 if (!recycle || !PushRecycleGroupCache(item))
957                     Utility.Dispose(item);
958                 return;
959             }
960
961             base.UnrealizeItem(item, recycle);
962         }
963
964         internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
965         {
966             if (suppressSelectionChangeNotification)
967             {
968                 return;
969             }
970
971             foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
972             {
973                 if (item.BindingContext == null) continue;
974                 if (newSelection.Contains(item.BindingContext))
975                 {
976                     if (!item.IsSelected) item.IsSelected = true;
977                 }
978                 else
979                 {
980                     if (item.IsSelected) item.IsSelected = false;
981                 }
982             }
983             SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
984
985             OnPropertyChanged(SelectedItemsProperty.PropertyName);
986         }
987
988         /// <summary>
989         /// Internal selection callback.
990         /// </summary>
991         /// <since_tizen> 9 </since_tizen>
992         protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
993         {
994             //Selection Callback
995         }
996
997         /// <summary>
998         /// Adjust scrolling position by own scrolling rules.
999         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1000         /// </summary>
1001         /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
1002         /// <returns>Adjusted scroll destination</returns>
1003         [EditorBrowsable(EditorBrowsableState.Never)]
1004         protected override float AdjustTargetPositionOfScrollAnimation(float position)
1005         {
1006             // Destination is depending on implementation of layout manager.
1007             // Get destination from layout manager.
1008             return ItemsLayouter?.CalculateCandidateScrollPosition(position) ?? position;
1009         }
1010
1011         /// <inheritdoc/>
1012         [EditorBrowsable(EditorBrowsableState.Never)]
1013         protected override void ClearCache()
1014         {
1015             foreach (RecyclerViewItem item in recycleGroupHeaderCache)
1016             {
1017                 Utility.Dispose(item);
1018             }
1019             recycleGroupHeaderCache.Clear();
1020             foreach (RecyclerViewItem item in recycleGroupFooterCache)
1021             {
1022                 Utility.Dispose(item);
1023             }
1024             recycleGroupFooterCache.Clear();
1025             base.ClearCache();
1026         }
1027
1028
1029         /// <summary>
1030         /// OnScroll event callback. Requesting layout to the layouter with given scrollPosition.
1031         /// </summary>
1032         /// <param name="source">Scroll source object</param>
1033         /// <param name="args">Scroll event argument</param>
1034         /// <since_tizen> 9 </since_tizen>
1035         protected override void OnScrolling(object source, ScrollEventArgs args)
1036         {
1037             if (disposed) return;
1038
1039             if (needInitalizeLayouter && (ItemsLayouter != null))
1040             {
1041                 ItemsLayouter.Initialize(this);
1042                 needInitalizeLayouter = false;
1043             }
1044
1045             base.OnScrolling(source, args);
1046         }
1047
1048         /// <summary>
1049         /// Dispose ItemsView and all children on it.
1050         /// </summary>
1051         /// <param name="type">Dispose type.</param>
1052         /// <since_tizen> 9 </since_tizen>
1053         protected override void Dispose(DisposeTypes type)
1054         {
1055             if (disposed)
1056             {
1057                 return;
1058             }
1059
1060             if (type == DisposeTypes.Explicit)
1061             {
1062                 // From now on, no need to use this properties,
1063                 // so remove reference, to push it into garbage collector.
1064
1065                 // Arugable to disposing user-created members.
1066                 /*
1067                 if (Header != null)
1068                 {
1069                     Utility.Dispose(Header);
1070                     Header = null;
1071                 }
1072                 if (Footer != null)
1073                 {
1074                     Utility.Dispose(Footer);
1075                     Footer = null;
1076                 }
1077                 */
1078
1079                 groupHeaderTemplate = null;
1080                 groupFooterTemplate = null;
1081
1082                 if (selectedItem != null) 
1083                 {
1084                     selectedItem = null;
1085                 }
1086                 if (selectedItems != null)
1087                 {
1088                     selectedItems.Clear();
1089                     selectedItems = null;
1090                 }
1091                 if (InternalItemSource != null)
1092                 {
1093                     InternalItemSource.Dispose();
1094                     InternalItemSource = null;
1095                 }
1096             }
1097
1098             base.Dispose(type);
1099         }
1100
1101         private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
1102         {
1103             var command = colView.SelectionChangedCommand;
1104
1105             if (command != null)
1106             {
1107                 var commandParameter = colView.SelectionChangedCommandParameter;
1108
1109                 if (command.CanExecute(commandParameter))
1110                 {
1111                     command.Execute(commandParameter);
1112                 }
1113             }
1114             colView.SelectionChanged?.Invoke(colView, args);
1115             colView.OnSelectionChanged(args);
1116         }
1117
1118         private static object CoerceSelectedItems(BindableObject bindable, object value)
1119         {
1120             if (value == null)
1121             {
1122                 return new SelectionList((CollectionView)bindable);
1123             }
1124
1125             if (value is SelectionList)
1126             {
1127                 return value;
1128             }
1129
1130             return new SelectionList((CollectionView)bindable, value as IList<object>);
1131         }
1132
1133         private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
1134         {
1135             var colView = (CollectionView)bindable;
1136
1137             var oldMode = (ItemSelectionMode)oldValue;
1138             var newMode = (ItemSelectionMode)newValue;
1139
1140             IList<object> previousSelection = new List<object>();
1141             IList<object> newSelection = new List<object>();
1142
1143             switch (oldMode)
1144             {
1145                 case ItemSelectionMode.None:
1146                     break;
1147                 case ItemSelectionMode.Single:
1148                     if (colView.SelectedItem != null)
1149                     {
1150                         previousSelection.Add(colView.SelectedItem);
1151                     }
1152                     break;
1153                 case ItemSelectionMode.Multiple:
1154                     previousSelection = colView.SelectedItems;
1155                     break;
1156             }
1157
1158             switch (newMode)
1159             {
1160                 case ItemSelectionMode.None:
1161                     break;
1162                 case ItemSelectionMode.Single:
1163                     if (colView.SelectedItem != null)
1164                     {
1165                         newSelection.Add(colView.SelectedItem);
1166                     }
1167                     break;
1168                 case ItemSelectionMode.Multiple:
1169                     newSelection = colView.SelectedItems;
1170                     break;
1171             }
1172
1173             if (previousSelection.Count == newSelection.Count)
1174             {
1175                 if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
1176                 {
1177                     // Both selections are empty or have the same single item; no reason to signal a change
1178                     return;
1179                 }
1180             }
1181
1182             var args = new SelectionChangedEventArgs(previousSelection, newSelection);
1183             SelectionPropertyChanged(colView, args);
1184         }
1185
1186         private void Init()
1187         {
1188             if (ItemsSource == null) return;
1189             if (ItemsLayouter == null) return;
1190             if (ItemTemplate == null) return;
1191
1192             if (disposed) return;
1193             if (needInitalizeLayouter)
1194             {
1195                 if (InternalItemSource == null) return;
1196
1197                 InternalItemSource.HasHeader = (header != null);
1198                 InternalItemSource.HasFooter = (footer != null);
1199             }
1200
1201             if (!wasRelayouted) return;
1202
1203             if (needInitalizeLayouter)
1204             {
1205                 itemsLayouter.Clear();
1206                 ClearCache();
1207
1208                 ItemsLayouter.Initialize(this);
1209                 needInitalizeLayouter = false;
1210             }
1211             ItemsLayouter.RequestLayout(0.0f, true);
1212
1213             if (delayedScrollTo)
1214             {
1215                 delayedScrollTo = false;
1216                 ScrollTo(delayedScrollToParam.position, delayedScrollToParam.anim);
1217             }
1218
1219             if (delayedIndexScrollTo)
1220             {
1221                 delayedIndexScrollTo = false;
1222                 ScrollTo(delayedIndexScrollToParam.index, delayedIndexScrollToParam.anim, delayedIndexScrollToParam.scrollTo);
1223             }
1224
1225             if (ScrollingDirection == Direction.Horizontal)
1226             {
1227                 ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
1228             }
1229             else
1230             {
1231                 ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
1232             }
1233         }
1234
1235         private bool PushRecycleGroupCache(RecyclerViewItem item)
1236         {
1237             if (item == null) throw new ArgumentNullException(nameof(item));
1238             if (RecycleCache.Count >= 20) return false;
1239             if (item.Template == null) return false;
1240             if (item.isGroupHeader)
1241             {
1242                 recycleGroupHeaderCache.Add(item);
1243             }
1244             else if (item.isGroupFooter)
1245             {
1246                 recycleGroupFooterCache.Add(item);
1247             }
1248             else return false;
1249             item.Hide();
1250             item.Index = -1;
1251             return true;
1252         }
1253
1254         private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
1255         {
1256             RecyclerViewItem viewItem = null;
1257
1258             var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
1259             for (int i = 0; i < Cache.Count; i++)
1260             {
1261                 viewItem = Cache[i];
1262                 if (Template == viewItem.Template) break;
1263             }
1264
1265             if (viewItem != null)
1266             {
1267                 Cache.Remove(viewItem);
1268                 viewItem.Show();
1269             }
1270             return viewItem;
1271         }
1272         private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
1273         {
1274             switch (args.Action)
1275             {
1276                 case NotifyCollectionChangedAction.Add:
1277                     break;
1278                 case NotifyCollectionChangedAction.Remove:
1279                     // Clear removed items.
1280                     if (args.OldItems != null)
1281                     {
1282                         if (args.OldItems.Contains(selectedItem))
1283                         {
1284                             selectedItem = null;
1285                         }
1286                         
1287                         if (selectedItems != null)
1288                         {
1289                             foreach (object removed in args.OldItems)
1290                             {
1291                                 if (selectedItems.Contains(removed))
1292                                 {
1293                                     selectedItems.Remove(removed);
1294                                 }
1295                             }
1296                         }
1297                     }
1298                     break;
1299                 case NotifyCollectionChangedAction.Replace:
1300                     break;
1301                 case NotifyCollectionChangedAction.Move:
1302                     break;
1303                 case NotifyCollectionChangedAction.Reset:
1304                     break;
1305                 default:
1306                     throw new ArgumentOutOfRangeException(nameof(args));
1307             }
1308         }
1309
1310     }
1311 }