00964ccbb53643172797073efa54ac369da1e161
[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         /// <summary>
54         /// Container which contains ViewItems.
55         /// </summary>
56         [EditorBrowsable(EditorBrowsableState.Never)]
57         protected View Container { get; set; }
58
59         /// <summary>
60         /// Parent ItemsView.
61         /// </summary>
62         [EditorBrowsable(EditorBrowsableState.Never)]
63         protected RecyclerView ItemsView { get; set; }
64
65         /// <summary>
66         /// The last scrolled position which is calculated by ScrollableBase. The value should be updated in the Recycle() method.
67         /// </summary>
68         [EditorBrowsable(EditorBrowsableState.Never)]
69         protected float PrevScrollPosition { get; set; }
70
71         /// <summary>
72         /// First index of visible items.
73         /// </summary>
74         [EditorBrowsable(EditorBrowsableState.Never)]
75         protected int FirstVisible { get; set; } = -1;
76
77         /// <summary>
78         /// Last index of visible items.
79         /// </summary>
80         [EditorBrowsable(EditorBrowsableState.Never)]
81         protected int LastVisible { get; set; } = -1;
82
83         /// <summary>
84         /// Visible ViewItem.
85         /// </summary>
86         [EditorBrowsable(EditorBrowsableState.Never)]
87         protected List<RecyclerViewItem> VisibleItems { get; } = new List<RecyclerViewItem>();
88
89         /// <summary>
90         /// Flag of layouter initialization.
91         /// </summary>
92         [EditorBrowsable(EditorBrowsableState.Never)]
93         protected bool IsInitialized { get; set; } = false;
94
95         /// <summary>
96         /// Candidate item step size for scroll size measure.
97         /// </summary>
98         [EditorBrowsable(EditorBrowsableState.Never)]
99         protected float StepCandidate { get; set; }
100
101         /// <summary>
102         /// Candidate item's Margin for scroll size measure.
103         /// </summary>
104         [EditorBrowsable(EditorBrowsableState.Never)]
105         protected Extents CandidateMargin { get; set; }
106
107         /// <summary>
108         /// Content size of scrollable.
109         /// </summary>
110         [EditorBrowsable(EditorBrowsableState.Never)]
111         protected float ScrollContentSize { get; set; }
112
113         /// <summary>
114         /// boolean flag of scrollable horizontal direction.
115         /// </summary>
116         [EditorBrowsable(EditorBrowsableState.Never)]
117         protected bool IsHorizontal { get; set; }
118
119         /// <summary>
120         /// Clean up ItemsLayouter.
121         /// </summary>
122         /// <param name="view"> ItemsView of layouter.</param>
123         /// <since_tizen> 9 </since_tizen>
124         public virtual void Initialize(RecyclerView view)
125         {
126             ItemsView = view ?? throw new ArgumentNullException(nameof(view));
127             Container = view.ContentContainer;
128             PrevScrollPosition = 0.0f;
129
130             IsHorizontal = (view.ScrollingDirection == ScrollableBase.Direction.Horizontal);
131
132             IsInitialized = true;
133         }
134
135         /// <summary>
136         /// This is called to find out where items are lain out according to current scroll position.
137         /// </summary>
138         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
139         /// <param name="force">boolean force flag to layouting forcely.</param>
140         /// <since_tizen> 9 </since_tizen>
141         public virtual void RequestLayout(float scrollPosition, bool force = false)
142         {
143             // Layouting Items in scrollPosition.
144         }
145
146         /// <summary>
147         /// Clear the current screen and all properties.
148         /// </summary>
149         /// <since_tizen> 9 </since_tizen>
150         public virtual void Clear()
151         {
152             if (VisibleItems != null)
153             {
154                 foreach (RecyclerViewItem item in VisibleItems)
155                 {
156                     if (ItemsView != null) ItemsView.UnrealizeItem(item, false);
157                }
158                 VisibleItems.Clear();
159             }
160             if (CandidateMargin != null)
161             {
162                 CandidateMargin.Dispose();
163                 CandidateMargin = null;
164             }
165             if (Container)
166             {
167                 Container.Size = ItemsView.Size;
168                 Container.Position = new Position(0.0f, 0.0f);
169                 Container = null;
170             }
171             ItemsView = null;
172         }
173
174         /// <summary>
175         /// This is called to find out how much container size can be.
176         /// </summary>
177         [EditorBrowsable(EditorBrowsableState.Never)]
178         public virtual float CalculateLayoutOrientationSize()
179         {
180             return 0.0f;
181         }
182
183         /// <summary>
184         /// Adjust scrolling position by own scrolling rules.
185         /// </summary>
186         /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
187         [EditorBrowsable(EditorBrowsableState.Never)]
188         public virtual float CalculateCandidateScrollPosition(float scrollPosition)
189         {
190             return scrollPosition;
191         }
192
193         /// <summary>
194         /// Notify the relayout of ViewItem.
195         /// </summary>
196         /// <param name="item">updated ViewItem.</param>
197         [EditorBrowsable(EditorBrowsableState.Never)]
198         public virtual void NotifyItemSizeChanged(RecyclerViewItem item)
199         {
200         }
201
202         /// <summary>
203         /// Notify the dataset is Changed.
204         /// </summary>
205         [EditorBrowsable(EditorBrowsableState.Never)]
206         public virtual void NotifyDataSetChanged()
207         {
208             Initialize(ItemsView);
209         }
210
211         /// <summary>
212         /// Notify the observable item in startIndex is changed.
213         /// </summary>
214         /// <param name="source">Dataset source.</param>
215         /// <param name="startIndex">Changed item index.</param>
216         [EditorBrowsable(EditorBrowsableState.Never)]
217         public virtual void NotifyItemChanged(IItemSource source, int startIndex)
218         {
219         }
220
221         /// <summary>
222         /// Notify the observable item is inserted in dataset.
223         /// </summary>
224         /// <param name="source">Dataset source.</param>
225         /// <param name="startIndex">Inserted item index.</param>
226         [EditorBrowsable(EditorBrowsableState.Never)]
227         public virtual void NotifyItemInserted(IItemSource source, int startIndex)
228         {
229         }
230
231         /// <summary>
232         /// Notify the observable item is moved from fromPosition to ToPosition.
233         /// </summary>
234         /// <param name="source">Dataset source.</param>
235         /// <param name="fromPosition">Previous item position.</param>
236         /// <param name="toPosition">Moved item position.</param>
237         [EditorBrowsable(EditorBrowsableState.Never)]
238         public virtual void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
239         {
240         }
241
242         /// <summary>
243         /// Notify the range of the observable items are moved from fromPosition to ToPosition.
244         /// </summary>
245         /// <param name="source"></param>
246         /// <param name="fromPosition"></param>
247         /// <param name="toPosition"></param>
248         /// <param name="count"></param>
249         [EditorBrowsable(EditorBrowsableState.Never)]
250         public virtual void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
251         {
252         }
253
254         /// <summary>
255         /// Notify the range of observable items from start to end are changed.
256         /// </summary>
257         /// <param name="source">Dataset source.</param>
258         /// <param name="startRange">Start index of changed items range.</param>
259         /// <param name="endRange">End index of changed items range.</param>
260         [EditorBrowsable(EditorBrowsableState.Never)]
261         public virtual void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
262         {
263         }
264
265         /// <summary>
266         /// Notify the count range of observable items are inserted in startIndex.
267         /// </summary>
268         /// <param name="source">Dataset source.</param>
269         /// <param name="startIndex">Start index of inserted items range.</param>
270         /// <param name="count">The number of inserted items.</param>
271         [EditorBrowsable(EditorBrowsableState.Never)]
272         public virtual void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
273         {
274         }
275
276         /// <summary>
277         /// Notify the count range of observable items from the startIndex are removed.
278         /// </summary>
279         /// <param name="source">Dataset source.</param>
280         /// <param name="startIndex">Start index of removed items range.</param>
281         /// <param name="count">The number of removed items</param>
282         [EditorBrowsable(EditorBrowsableState.Never)]
283         public virtual void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
284         {
285         }
286
287         /// <summary>
288         /// Notify the observable item in startIndex is removed.
289         /// </summary>
290         /// <param name="source">Dataset source.</param>
291         /// <param name="startIndex">Index of removed item.</param>
292         [EditorBrowsable(EditorBrowsableState.Never)]
293         public virtual void NotifyItemRemoved(IItemSource source, int startIndex)
294         {
295         }
296
297         /// <summary>
298         /// Gets the next keyboard focusable view in this control towards the given direction.<br />
299         /// A control needs to override this function in order to support two dimensional keyboard navigation.<br />
300         /// </summary>
301         /// <param name="currentFocusedView">The current focused view.</param>
302         /// <param name="direction">The direction to move the focus towards.</param>
303         /// <param name="loopEnabled">Whether the focus movement should be looped within the control.</param>
304         /// <returns>The next keyboard focusable view in this control or an empty handle if no view can be focused.</returns>
305         [EditorBrowsable(EditorBrowsableState.Never)]
306         public virtual View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
307         {
308             return null;
309         }
310
311         /// <summary>
312         /// Dispose ItemsLayouter and all children on it.
313         /// </summary>
314         /// <since_tizen> 9 </since_tizen>
315         public void Dispose()
316         {
317             Dispose(true);
318             GC.SuppressFinalize(this);
319         }
320
321         /// <summary>
322         /// Measure the size of child ViewItem manually.
323         /// </summary>
324         /// <param name="parent">Parent ItemsView.</param>
325         /// <param name="child">Child ViewItem to Measure()</param>
326         [EditorBrowsable(EditorBrowsableState.Never)]
327         protected virtual void MeasureChild(RecyclerView parent, RecyclerViewItem child)
328         {
329             if (parent == null) throw new ArgumentNullException(nameof(parent));
330             if (child == null) throw new ArgumentNullException(nameof(child));
331
332             if (child.Layout == null) return;
333
334             //FIXME: This measure can be restricted size of child to be less than parent size.
335             // but in some multiple-line TextLabel can be long enough to over the it's parent size.
336
337             MeasureSpecification childWidthMeasureSpec = LayoutGroup.GetChildMeasureSpecification(
338                         new MeasureSpecification(new LayoutLength(parent.Size.Width - parent.Padding.Start - parent.Padding.End - child.Margin.Start - child.Margin.End), MeasureSpecification.ModeType.Exactly),
339                         new LayoutLength(0),
340                         new LayoutLength(child.WidthSpecification));
341
342             MeasureSpecification childHeightMeasureSpec = LayoutGroup.GetChildMeasureSpecification(
343                         new MeasureSpecification(new LayoutLength(parent.Size.Height - parent.Padding.Top - parent.Padding.Bottom - child.Margin.Top - child.Margin.Bottom), MeasureSpecification.ModeType.Exactly),
344                         new LayoutLength(0),
345                         new LayoutLength(child.HeightSpecification));
346
347             child.Layout.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
348         }
349
350         /// <summary>
351         /// Find consecutive visible items index.
352         /// </summary>
353         /// <param name="visibleArea">float turple of visible area start position to end position. </param>
354         /// <return>int turple of start index to end index</return>
355         [EditorBrowsable(EditorBrowsableState.Never)]
356         protected virtual (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
357         {
358             return (0, 0);
359         }
360
361         /// <summary>
362         /// Dispose ItemsLayouter and all children on it.
363         /// </summary>
364         /// <param name="disposing">true when it disposed by Dispose(). </param>
365         [EditorBrowsable(EditorBrowsableState.Never)]
366         protected virtual void Dispose(bool disposing)
367         {
368             if (disposed)
369             {
370                 return;
371             }
372
373             disposed = true;
374             if (disposing)
375             {
376                 Clear();
377                 padding.Dispose();
378             }
379         }
380
381         internal virtual (float X, float Y) GetItemPosition(int index)
382         {
383             return (0, 0);
384         }
385
386         internal virtual (float Width, float Height) GetItemSize(int index)
387         {
388             return (0, 0);
389         }
390     }
391 }