[NUI] Introduce CollectionView and related classes. (#2525)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / RecyclerView / ItemSource / MarshalingObservableCollection.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
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Collections.Specialized;
21
22 namespace Tizen.NUI.Components
23 {
24     // Wraps a List which implements INotifyCollectionChanged (usually an ObservableCollection)
25     // and marshals all of the list modifications to the main thread. Modifications to the underlying 
26     // collection which are made off of the main thread remain invisible to consumers on the main thread
27     // until they have been processed by the main thread.
28
29     internal class MarshalingObservableCollection : List<object>, INotifyCollectionChanged
30     {
31         readonly IList internalCollection;
32
33         public MarshalingObservableCollection(IList list)
34         {
35             if (!(list is INotifyCollectionChanged incc))
36             {
37                 throw new ArgumentException($"{nameof(list)} must implement {nameof(INotifyCollectionChanged)}");
38             }
39
40             internalCollection = list;
41             incc.CollectionChanged += InternalCollectionChanged;
42
43             foreach (var item in internalCollection)
44             {
45                 Add(item);
46             }
47         }
48
49         class ResetNotifyCollectionChangedEventArgs : NotifyCollectionChangedEventArgs
50         {
51             public IList Items { get; }
52             public ResetNotifyCollectionChangedEventArgs(IList items)
53                 : base(NotifyCollectionChangedAction.Reset) => Items = items;
54         }
55
56         public event NotifyCollectionChangedEventHandler CollectionChanged;
57
58         void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
59         {
60             CollectionChanged?.Invoke(this, args);
61         }
62
63         void InternalCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
64         {
65             if (args.Action == NotifyCollectionChangedAction.Reset)
66             {
67                 var items = new List<object>();
68                 for (int n = 0; n < internalCollection.Count; n++)
69                 {
70                     items.Add(internalCollection[n]);
71                 }
72
73                 args = new ResetNotifyCollectionChangedEventArgs(items);
74             }
75 /*
76             if (Device.IsInvokeRequired)
77             {
78                 Device.BeginInvokeOnMainThread(() => HandleCollectionChange(args));
79             }
80             else
81             {
82                 HandleCollectionChange(args);
83             }
84 */
85
86             HandleCollectionChange(args);
87         }
88
89         void HandleCollectionChange(NotifyCollectionChangedEventArgs args)
90         {
91             switch (args.Action)
92             {
93                 case NotifyCollectionChangedAction.Add:
94                     Add(args);
95                     break;
96                 case NotifyCollectionChangedAction.Move:
97                     Move(args);
98                     break;
99                 case NotifyCollectionChangedAction.Remove:
100                     Remove(args);
101                     break;
102                 case NotifyCollectionChangedAction.Replace:
103                     Replace(args);
104                     break;
105                 case NotifyCollectionChangedAction.Reset:
106                     Reset(args);
107                     break;
108             }
109         }
110
111         void Move(NotifyCollectionChangedEventArgs args)
112         {
113             var count = args.OldItems.Count;
114
115             for (int n = 0; n < count; n++)
116             {
117                 var toMove = this[args.OldStartingIndex];
118                 RemoveAt(args.OldStartingIndex);
119                 Insert(args.NewStartingIndex, toMove);
120             }
121
122             OnCollectionChanged(args);
123         }
124
125         void Remove(NotifyCollectionChangedEventArgs args)
126         {
127             var startIndex = args.OldStartingIndex + args.OldItems.Count - 1;
128             for (int n = startIndex; n >= args.OldStartingIndex; n--)
129             {
130                 RemoveAt(n);
131             }
132
133             OnCollectionChanged(args);
134         }
135
136         void Replace(NotifyCollectionChangedEventArgs args)
137         {
138             var startIndex = args.NewStartingIndex;
139             foreach (var item in args.NewItems)
140             {
141                 this[startIndex] = item;
142                 startIndex += 1;
143             }
144
145             OnCollectionChanged(args);
146         }
147
148         void Add(NotifyCollectionChangedEventArgs args)
149         {
150             var startIndex = args.NewStartingIndex;
151             foreach (var item in args.NewItems)
152             {
153                 Insert(startIndex, item);
154                 startIndex += 1;
155             }
156
157             OnCollectionChanged(args);
158         }
159
160         void Reset(NotifyCollectionChangedEventArgs args)
161         {
162             if (!(args is ResetNotifyCollectionChangedEventArgs resetArgs))
163             {
164                 throw new InvalidOperationException($"Cannot guarantee collection accuracy for Resets which do not use {nameof(ResetNotifyCollectionChangedEventArgs)}");
165             }
166
167             Clear();
168             foreach (var item in resetArgs.Items)
169             {
170                 Add(item);
171             }
172
173             OnCollectionChanged(args);
174         }
175     }
176 }