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