[NUI] refactoring collectionView and Layouters for extended class (#5123)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / RecyclerView / Layouter / ItemsLayouter.cs
1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 using System;
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20
21 namespace Tizen.NUI.Components
22 {
23     /// <summary>
24     /// Default layout manager for RecyclerView.
25     /// Layouting RecyclerViewItem on the scroll ContentContainer
26     /// which need to be visible on the view by scroll position.
27     /// </summary>
28     /// <since_tizen> 9 </since_tizen>
29     public abstract class ItemsLayouter : ICollectionChangedNotifier, IDisposable
30     {
31         private bool disposed = false;
32         private Extents padding = new Extents(0, 0, 0, 0);
33
34         /// <summary>
35         /// Padding for ContentContainer of RecyclerView.
36         /// </summary>
37         [EditorBrowsable(EditorBrowsableState.Never)]
38         public Extents Padding {
39             get
40             {
41                 return padding;
42             }
43             set
44             {
45                 padding = value;
46                 if (ItemsView?.ContentContainer != null)
47                 {
48                     ItemsView.Padding = padding;
49                 }
50             }
51         }
52
53
54         /// <summary>
55         /// Internal item source that organized.
56         /// Check IItemSource and IGrouppedItemSoure also.
57         /// </summary>
58         [EditorBrowsable(EditorBrowsableState.Never)]
59         protected IItemSource Source => ItemsView?.InternalSource;
60
61         /// <summary>
62         /// Container which contains ViewItems.
63         /// </summary>
64         [EditorBrowsable(EditorBrowsableState.Never)]
65         protected View Container =>ItemsView?.ContentContainer;
66
67         /// <summary>
68         /// Parent ItemsView.
69         /// </summary>
70         [EditorBrowsable(EditorBrowsableState.Never)]
71         protected RecyclerView ItemsView { get; set; }
72
73         /// <summary>
74         /// The last scrolled position which is calculated by ScrollableBase. The value should be updated in the Recycle() method.
75         /// </summary>
76         [EditorBrowsable(EditorBrowsableState.Never)]
77         protected float PrevScrollPosition { get; set; }
78
79         /// <summary>
80         /// First index of visible items.
81         /// </summary>
82         [EditorBrowsable(EditorBrowsableState.Never)]
83         protected int FirstVisible { get; set; } = -1;
84
85         /// <summary>
86         /// Last index of visible items.
87         /// </summary>
88         [EditorBrowsable(EditorBrowsableState.Never)]
89         protected int LastVisible { get; set; } = -1;
90
91         /// <summary>
92         /// Visible ViewItem.
93         /// </summary>
94         [EditorBrowsable(EditorBrowsableState.Never)]
95         protected List<RecyclerViewItem> VisibleItems { get; } = new List<RecyclerViewItem>();
96
97         /// <summary>
98         /// Visible ViewItem.
99         /// </summary>
100         [EditorBrowsable(EditorBrowsableState.Never)]
101         protected virtual List<GroupInfo> GroupItems { get; }
102
103         /// <summary>
104         /// Flag of layouter initialization.
105         /// </summary>
106         [EditorBrowsable(EditorBrowsableState.Never)]
107         protected bool IsInitialized { get; set; } = false;
108
109         /// <summary>
110         /// Candidate item step size for scroll size measure.
111         /// </summary>
112         [EditorBrowsable(EditorBrowsableState.Never)]
113         protected float StepCandidate { get; set; }
114
115         /// <summary>
116         /// Candidate item's Margin for scroll size measure.
117         /// </summary>
118         [EditorBrowsable(EditorBrowsableState.Never)]
119         protected Extents CandidateMargin { get; set; }
120
121         /// <summary>
122         /// Content size of scrollable.
123         /// </summary>
124         [EditorBrowsable(EditorBrowsableState.Never)]
125         protected float ScrollContentSize { get; set; }
126
127         /// <summary>
128         /// boolean flag of scrollable horizontal direction.
129         /// </summary>
130         [EditorBrowsable(EditorBrowsableState.Never)]
131         protected bool IsHorizontal { get; set; }
132
133         /// <summary>
134         /// Clean up ItemsLayouter.
135         /// </summary>
136         /// <param name="view"> ItemsView of layouter.</param>
137         /// <since_tizen> 9 </since_tizen>
138         public virtual void Initialize(RecyclerView view)
139         {
140             ItemsView = view ?? throw new ArgumentNullException(nameof(view));
141             PrevScrollPosition = 0.0f;
142
143             IsHorizontal = (view.ScrollingDirection == ScrollableBase.Direction.Horizontal);
144
145             IsInitialized = true;
146         }
147
148         /// <summary>
149         /// This is called to find out where items are lain out according to current scroll position.
150         /// </summary>
151         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
152         /// <param name="force">boolean force flag to layouting forcely.</param>
153         /// <since_tizen> 9 </since_tizen>
154         public virtual void RequestLayout(float scrollPosition, bool force = false)
155         {
156             // Layouting Items in scrollPosition.
157         }
158
159         /// <summary>
160         /// Clear the current screen and all properties.
161         /// </summary>
162         /// <since_tizen> 9 </since_tizen>
163         public virtual void Clear()
164         {
165             if (VisibleItems != null)
166             {
167                 foreach (RecyclerViewItem item in VisibleItems)
168                 {
169                     if (ItemsView != null) ItemsView.UnrealizeItem(item, false);
170                }
171                 VisibleItems.Clear();
172             }
173             if (CandidateMargin != null)
174             {
175                 CandidateMargin.Dispose();
176                 CandidateMargin = null;
177             }
178             if (Container)
179             {
180                 if (ItemsView != null) Container.Size = ItemsView.Size;
181                 Container.Position = new Position(0.0f, 0.0f);
182             }
183             ItemsView = null;
184         }
185
186         /// <summary>
187         /// This is called to find out how much container size can be.
188         /// </summary>
189         [EditorBrowsable(EditorBrowsableState.Never)]
190         public virtual float CalculateLayoutOrientationSize()
191         {
192             return 0.0f;
193         }
194
195         /// <summary>
196         /// Adjust scrolling position by own scrolling rules.
197         /// </summary>
198         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
199         [EditorBrowsable(EditorBrowsableState.Never)]
200         public virtual float CalculateCandidateScrollPosition(float scrollPosition)
201         {
202             return scrollPosition;
203         }
204
205         /// <summary>
206         /// Notify the relayout of ViewItem.
207         /// </summary>
208         /// <param name="item">updated ViewItem.</param>
209         [EditorBrowsable(EditorBrowsableState.Never)]
210         public virtual void NotifyItemSizeChanged(RecyclerViewItem item)
211         {
212         }
213
214         /// <summary>
215         /// Notify the dataset is Changed.
216         /// </summary>
217         [EditorBrowsable(EditorBrowsableState.Never)]
218         public virtual void NotifyDataSetChanged()
219         {
220             if (ItemsView != null)
221             {
222                 Initialize(ItemsView);
223             }
224         }
225
226         /// <summary>
227         /// Notify the observable item in startIndex is changed.
228         /// </summary>
229         /// <param name="source">Dataset source.</param>
230         /// <param name="startIndex">Changed item index.</param>
231         [EditorBrowsable(EditorBrowsableState.Never)]
232         public virtual void NotifyItemChanged(IItemSource source, int startIndex)
233         {
234         }
235
236         /// <summary>
237         /// Notify the observable item is inserted in dataset.
238         /// </summary>
239         /// <param name="source">Dataset source.</param>
240         /// <param name="startIndex">Inserted item index.</param>
241         [EditorBrowsable(EditorBrowsableState.Never)]
242         public virtual void NotifyItemInserted(IItemSource source, int startIndex)
243         {
244         }
245
246         /// <summary>
247         /// Notify the observable item is moved from fromPosition to ToPosition.
248         /// </summary>
249         /// <param name="source">Dataset source.</param>
250         /// <param name="fromPosition">Previous item position.</param>
251         /// <param name="toPosition">Moved item position.</param>
252         [EditorBrowsable(EditorBrowsableState.Never)]
253         public virtual void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
254         {
255         }
256
257         /// <summary>
258         /// Notify the range of the observable items are moved from fromPosition to ToPosition.
259         /// </summary>
260         /// <param name="source"></param>
261         /// <param name="fromPosition"></param>
262         /// <param name="toPosition"></param>
263         /// <param name="count"></param>
264         [EditorBrowsable(EditorBrowsableState.Never)]
265         public virtual void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
266         {
267         }
268
269         /// <summary>
270         /// Notify the range of observable items from start to end are changed.
271         /// </summary>
272         /// <param name="source">Dataset source.</param>
273         /// <param name="startRange">Start index of changed items range.</param>
274         /// <param name="endRange">End index of changed items range.</param>
275         [EditorBrowsable(EditorBrowsableState.Never)]
276         public virtual void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
277         {
278         }
279
280         /// <summary>
281         /// Notify the count range of observable items are inserted in startIndex.
282         /// </summary>
283         /// <param name="source">Dataset source.</param>
284         /// <param name="startIndex">Start index of inserted items range.</param>
285         /// <param name="count">The number of inserted items.</param>
286         [EditorBrowsable(EditorBrowsableState.Never)]
287         public virtual void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
288         {
289         }
290
291         /// <summary>
292         /// Notify the count range of observable items from the startIndex are removed.
293         /// </summary>
294         /// <param name="source">Dataset source.</param>
295         /// <param name="startIndex">Start index of removed items range.</param>
296         /// <param name="count">The number of removed items</param>
297         [EditorBrowsable(EditorBrowsableState.Never)]
298         public virtual void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
299         {
300         }
301
302         /// <summary>
303         /// Notify the observable item in startIndex is removed.
304         /// </summary>
305         /// <param name="source">Dataset source.</param>
306         /// <param name="startIndex">Index of removed item.</param>
307         [EditorBrowsable(EditorBrowsableState.Never)]
308         public virtual void NotifyItemRemoved(IItemSource source, int startIndex)
309         {
310         }
311
312         /// <summary>
313         /// Gets the next keyboard focusable view in this control towards the given direction.<br />
314         /// A control needs to override this function in order to support two dimensional keyboard navigation.<br />
315         /// </summary>
316         /// <param name="currentFocusedView">The current focused view.</param>
317         /// <param name="direction">The direction to move the focus towards.</param>
318         /// <param name="loopEnabled">Whether the focus movement should be looped within the control.</param>
319         /// <returns>The next keyboard focusable view in this control or an empty handle if no view can be focused.</returns>
320         [EditorBrowsable(EditorBrowsableState.Never)]
321         public virtual View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
322         {
323             return null;
324         }
325
326         /// <summary>
327         /// Dispose ItemsLayouter and all children on it.
328         /// </summary>
329         /// <since_tizen> 9 </since_tizen>
330         public void Dispose()
331         {
332             Dispose(true);
333             GC.SuppressFinalize(this);
334         }
335
336         /// <summary>
337         /// Measure the size of child ViewItem manually.
338         /// </summary>
339         /// <param name="parent">Parent ItemsView.</param>
340         /// <param name="child">Child ViewItem to Measure()</param>
341         [EditorBrowsable(EditorBrowsableState.Never)]
342         protected virtual void MeasureChild(RecyclerView parent, RecyclerViewItem child)
343         {
344             if (parent == null) throw new ArgumentNullException(nameof(parent));
345             if (child == null) throw new ArgumentNullException(nameof(child));
346
347             if (child.Layout == null) return;
348
349             //FIXME: This measure can be restricted size of child to be less than parent size.
350             // but our parent can be not calculated yet, also some child can be bigger than it's parent size,
351             // so we use implicit value 9999 as restricted specification.
352             MeasureSpecification childWidthMeasureSpec = LayoutGroup.GetChildMeasureSpecification(
353                         new MeasureSpecification(new LayoutLength(9999), MeasureSpecification.ModeType.Exactly),
354                         new LayoutLength(0),
355                         new LayoutLength(child.WidthSpecification));
356
357             MeasureSpecification childHeightMeasureSpec = LayoutGroup.GetChildMeasureSpecification(
358                         new MeasureSpecification(new LayoutLength(9999), MeasureSpecification.ModeType.Exactly),
359                         new LayoutLength(0),
360                         new LayoutLength(child.HeightSpecification));
361
362             child.Layout.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
363         }
364
365         /// <summary>
366         /// Find consecutive visible items index.
367         /// </summary>
368         /// <param name="visibleArea">float turple of visible area start position to end position. </param>
369         /// <return>int turple of start index to end index</return>
370         [EditorBrowsable(EditorBrowsableState.Never)]
371         protected virtual (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
372         {
373             return (0, 0);
374         }
375
376         /// <summary>
377         /// Dispose ItemsLayouter and all children on it.
378         /// </summary>
379         /// <param name="disposing">true when it disposed by Dispose(). </param>
380         [EditorBrowsable(EditorBrowsableState.Never)]
381         protected virtual void Dispose(bool disposing)
382         {
383             if (disposed)
384             {
385                 return;
386             }
387
388             disposed = true;
389             if (disposing)
390             {
391                 Clear();
392                 padding.Dispose();
393             }
394         }
395
396         /// <summary>
397         /// Get item position.
398         /// </summary>
399         [EditorBrowsable(EditorBrowsableState.Never)]
400         protected internal virtual (float X, float Y) GetItemPosition(int index)
401         {
402             return (0, 0);
403         }
404
405         /// <summary>
406         /// Get item size.
407         /// </summary>
408         [EditorBrowsable(EditorBrowsableState.Never)]
409         protected internal virtual (float Width, float Height) GetItemSize(int index)
410         {
411             return (0, 0);
412         }
413
414         /// <summary>
415         /// Get visible item object on index if it is realized.
416         /// </summary>
417         [EditorBrowsable(EditorBrowsableState.Never)]
418         protected virtual RecyclerViewItem GetVisibleItem(int index)
419         {
420
421             foreach (RecyclerViewItem item in VisibleItems)
422             {
423                 if (item.Index == index) return item;
424             }
425             return null;
426         }
427
428         /// <summary>
429         /// The data class for group informations.
430         /// inherited class can use this data to managing group items feature.
431         /// </summary>
432         [EditorBrowsable(EditorBrowsableState.Never)]
433         protected internal class GroupInfo
434         {
435             /// <summary>
436             /// Group parent object.
437             /// </summary>
438             [EditorBrowsable(EditorBrowsableState.Never)]
439             public object GroupParent;
440
441             /// <summary>
442             /// Group start index.
443             /// </summary>
444             [EditorBrowsable(EditorBrowsableState.Never)]
445             public int StartIndex;
446
447             /// <summary>
448             /// Group count.
449             /// </summary>
450             [EditorBrowsable(EditorBrowsableState.Never)]
451             public int Count;
452
453             /// <summary>
454             /// Group size. this value is size of scrollable axis only.
455             /// </summary>
456             [EditorBrowsable(EditorBrowsableState.Never)]
457             public float GroupSize;
458
459             /// <summary>
460             /// Group position. this value is size of scrollable axis only.
461             /// </summary>
462             [EditorBrowsable(EditorBrowsableState.Never)]
463             public float GroupPosition;
464
465             /// <summary>
466             /// List of group items position.
467             /// </summary>
468             [EditorBrowsable(EditorBrowsableState.Never)]
469             public List<float> ItemPosition = new List<float>();
470         }
471     }
472 }