[NUI] Fix some svace issues.
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Wearable / src / public / RecyclerView / RecyclerView.cs
1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 using System;
17 using Tizen.NUI.BaseComponents;
18 using Tizen.NUI.Components;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21
22 namespace Tizen.NUI.Wearable
23 {
24     /// <summary>
25     /// [Draft] This class provides a View that can recycle items to improve performance.
26     /// </summary>
27     /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
28     [EditorBrowsable(EditorBrowsableState.Never)]
29     public class RecyclerView : ScrollableBase
30     {
31         private RecycleAdapter adapter;
32         private RecycleLayoutManager layoutManager;
33         private int totalItemCount = 15;
34         private List<PropertyNotification> notifications = new List<PropertyNotification>();
35
36         /// <summary>
37         /// Default constructor.
38         /// </summary>
39         public RecyclerView() : base()
40         {
41             Initialize(new RecycleAdapter(), new RecycleLayoutManager());
42         }
43
44         /// <summary>
45         /// A constructor of <see cref="RecyclerView" />.
46         /// </summary>
47         /// <param name="adapter">Recycle adapter of RecyclerView.</param>
48         /// <param name="layoutManager">Recycle layoutManager of RecyclerView.</param>
49         /// <since_tizen> 8 </since_tizen>
50         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
51         [EditorBrowsable(EditorBrowsableState.Never)]
52         public RecyclerView(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
53         {
54             Initialize(adapter, layoutManager);
55         }
56
57         private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
58         {
59             FocusGroup = true;
60             SetKeyboardNavigationSupport(true);
61             Scrolling += OnScrolling;
62
63             this.adapter = adapter;
64             this.adapter.OnDataChanged += OnAdapterDataChanged;
65
66             this.layoutManager = layoutManager;
67             this.layoutManager.Container = ContentContainer;
68             this.layoutManager.ItemSize = this.adapter.CreateRecycleItem().Size;
69             this.layoutManager.DataCount = this.adapter.Data.Count;
70
71             InitializeItems();
72         }
73
74         private void OnItemSizeChanged(object source, PropertyNotification.NotifyEventArgs args)
75         {
76             layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
77         }
78
79         public int TotalItemCount
80         {
81             get
82             {
83                 return totalItemCount;
84             }
85             set
86             {
87                 totalItemCount = value;
88                 InitializeItems();
89             }
90         }
91
92         private void InitializeItems()
93         {
94             for (int i = Children.Count - 1; i > -1; i--)
95             {
96                 Children[i].Unparent();
97                 notifications[i].Notified -= OnItemSizeChanged;
98                 notifications.RemoveAt(i);
99             }
100
101             for (int i = 0; i < totalItemCount; i++)
102             {
103                 RecycleItem item = adapter.CreateRecycleItem();
104                 item.DataIndex = i;
105                 item.Name = "[" + i + "] recycle";
106
107                 if (i < adapter.Data.Count)
108                 {
109                     adapter.BindData(item);
110                 }
111                 Add(item);
112
113                 PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f));
114                 noti.Notified += OnItemSizeChanged;
115                 notifications.Add(noti);
116             }
117
118             layoutManager.Layout(0.0f);
119
120             if (ScrollingDirection == Direction.Horizontal)
121             {
122                 ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
123             }
124             else
125             {
126                 ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
127             }
128         }
129
130
131         public new Direction ScrollingDirection
132         {
133             get
134             {
135                 return base.ScrollingDirection;
136             }
137             set
138             {
139                 base.ScrollingDirection = value;
140
141                 if (ScrollingDirection == Direction.Horizontal)
142                 {
143                     ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
144                 }
145                 else
146                 {
147                     ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
148                 }
149             }
150         }
151
152         /// <summary>
153         /// Recycler adpater.
154         /// </summary>
155         /// <since_tizen> 8 </since_tizen>
156         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
157         [EditorBrowsable(EditorBrowsableState.Never)]
158         public RecycleAdapter Adapter
159         {
160             get
161             {
162                 return adapter;
163             }
164             set
165             {
166                 if (adapter != null)
167                 {
168                     adapter.OnDataChanged -= OnAdapterDataChanged;
169                 }
170
171                 adapter = value;
172                 adapter.OnDataChanged += OnAdapterDataChanged;
173                 layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
174                 layoutManager.DataCount = adapter.Data.Count;
175                 InitializeItems();
176             }
177         }
178
179         /// <summary>
180         /// Recycler layoutManager.
181         /// </summary>
182         /// <since_tizen> 8 </since_tizen>
183         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
184         [EditorBrowsable(EditorBrowsableState.Never)]
185         public RecycleLayoutManager LayoutManager
186         {
187             get
188             {
189                 return layoutManager;
190             }
191             set
192             {
193                 layoutManager = value;
194                 layoutManager.Container = ContentContainer;
195                 layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
196                 layoutManager.DataCount = adapter.Data.Count;
197                 InitializeItems();
198             }
199         }
200
201         private void OnScrolling(object source, ScrollEventArgs args)
202         {
203             layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
204             List<RecycleItem> recycledItemList = layoutManager.Recycle(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
205             BindData(recycledItemList);
206         }
207
208         private void OnAdapterDataChanged(object source, EventArgs args)
209         {
210             List<RecycleItem> changedData = new List<RecycleItem>();
211
212             foreach (RecycleItem item in Children)
213             {
214                 changedData.Add(item);
215             }
216
217             BindData(changedData);
218         }
219
220         private void BindData(List<RecycleItem> changedData)
221         {
222             foreach (RecycleItem item in changedData)
223             {
224                 if (item.DataIndex > -1 && item.DataIndex < adapter.Data.Count)
225                 {
226                     item.Show();
227                     item.Name = "[" + item.DataIndex + "]";
228                     adapter.BindData(item);
229                 }
230                 else
231                 {
232                     item.Hide();
233                 }
234             }
235         }
236
237         /// <summary>
238         /// Adjust scrolling position by own scrolling rules.
239         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
240         /// </summary>
241         /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
242         /// <returns>Adjusted scroll destination</returns>
243         /// <since_tizen> 8 </since_tizen>
244         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
245         [EditorBrowsable(EditorBrowsableState.Never)]
246         protected override float AdjustTargetPositionOfScrollAnimation(float position)
247         {
248             // Destination is depending on implementation of layout manager.
249             // Get destination from layout manager.
250             return layoutManager.CalculateCandidateScrollPosition(position);
251         }
252
253         private View focusedView;
254         private int prevFocusedDataIndex = 0;
255
256         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
257         {
258             View nextFocusedView = null;
259
260             if (!focusedView)
261             {
262                 // If focusedView is null, find child which has previous data index
263                 if (Children.Count > 0 && Adapter.Data.Count > 0)
264                 {
265                     for (int i = 0; i < Children.Count; i++)
266                     {
267                         if ((Children[i] is RecycleItem item) && (item.DataIndex == prevFocusedDataIndex))
268                         {
269                             nextFocusedView = item;
270                             break;
271                         }
272                     }
273                 }
274             }
275             else
276             {
277                 // If this is not first focus, request next focus to LayoutManager
278                 if (LayoutManager != null)
279                 {
280                     nextFocusedView = LayoutManager.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
281                 }
282             }
283
284             if (nextFocusedView != null)
285             {
286                 // Check next focused view is inside of visible area.
287                 // If it is not, move scroll position to make it visible.
288                 Position scrollPosition = ContentContainer.CurrentPosition;
289                 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
290
291                 float left = nextFocusedView.Position.X;
292                 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
293                 float top = nextFocusedView.Position.Y;
294                 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
295
296                 float visibleRectangleLeft = -scrollPosition.X;
297                 float visibleRectangleRight = -scrollPosition.X + Size.Width;
298                 float visibleRectangleTop = -scrollPosition.Y;
299                 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
300
301                 if (ScrollingDirection == Direction.Horizontal)
302                 {
303                     if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
304                     {
305                         targetPosition = left;
306                     }
307                     else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
308                     {
309                         targetPosition = right - Size.Width;
310                     }
311                 }
312                 else
313                 {
314                     if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
315                     {
316                         targetPosition = top;
317                     }
318                     else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
319                     {
320                         targetPosition = bottom - Size.Height;
321                     }
322                 }
323
324                 focusedView = nextFocusedView;
325                 if (nextFocusedView is RecycleItem item)
326                 {
327                     prevFocusedDataIndex = item.DataIndex;
328                 }
329
330                 ScrollTo(targetPosition, true);
331             }
332             else
333             {
334                 // If nextView is null, it means that we should move focus to outside of Control.
335                 // Return FocusableView depending on direction.
336                 switch (direction)
337                 {
338                     case View.FocusDirection.Left:
339                         {
340                             nextFocusedView = LeftFocusableView;
341                             break;
342                         }
343                     case View.FocusDirection.Right:
344                         {
345                             nextFocusedView = RightFocusableView;
346                             break;
347                         }
348                     case View.FocusDirection.Up:
349                         {
350                             nextFocusedView = UpFocusableView;
351                             break;
352                         }
353                     case View.FocusDirection.Down:
354                         {
355                             nextFocusedView = DownFocusableView;
356                             break;
357                         }
358                     default:
359                         break;
360                 }
361
362                 if (nextFocusedView)
363                 {
364                     focusedView = null;
365                 }
366                 else
367                 {
368                     //If FocusableView doesn't exist, not move focus.
369                     nextFocusedView = focusedView;
370                 }
371             }
372
373             return nextFocusedView;
374         }
375     }
376 }