[NUI] change to protected property from field (#1723)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / FlexibleView / LinearLayoutManager.Internal.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Tizen.NUI.Components
6 {
7     public partial class LinearLayoutManager
8     {
9         internal virtual void LayoutChunk(FlexibleViewRecycler recycler,
10             LayoutState layoutState, LayoutChunkResult result)
11         {
12             FlexibleViewViewHolder holder = layoutState.Next(recycler);
13             if (holder == null)
14             {
15                 // if we are laying out views in scrap, this may return null which means there is
16                 // no more items to layout.
17                 result.Finished = true;
18                 return;
19             }
20
21             if (mShouldReverseLayout == (layoutState.LayoutDirection == LayoutState.LAYOUT_START))
22                 AddView(holder);
23             else
24                 AddView(holder, 0);
25
26             result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder);
27
28             float left, top, width, height;
29             if (Orientation == VERTICAL)
30             {
31                 width = Width - PaddingLeft - PaddingRight;
32                 height = result.Consumed;
33                 left = PaddingLeft;
34                 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
35                 {
36                     top = layoutState.Offset;
37                 }
38                 else
39                 {
40                     top = layoutState.Offset - height;
41                 }
42                 LayoutChild(holder, left, top, width, height);
43             }
44             else
45             {
46                 width = result.Consumed;
47                 height = Height - PaddingTop - PaddingBottom;
48                 top = PaddingTop;
49                 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
50                 {
51                     left = layoutState.Offset;
52                 }
53                 else
54                 {
55                     left = layoutState.Offset - width;
56                 }
57                 LayoutChild(holder, left, top, width, height);
58             }
59
60             result.Focusable = true;
61         }
62
63         internal override FlexibleViewViewHolder OnFocusSearchFailed(FlexibleViewViewHolder focused, FlexibleViewLayoutManager.Direction direction, FlexibleViewRecycler recycler)
64         {
65             if (ChildCount == 0)
66             {
67                 return null;
68             }
69             int layoutDir = ConvertFocusDirectionToLayoutDirection(direction);
70             if (layoutDir == LayoutState.INVALID_LAYOUT)
71             {
72                 return null;
73             }
74             int maxScroll = (int)(MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace());
75             UpdateLayoutState(layoutDir, maxScroll, false);
76             mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
77             mLayoutState.Recycle = false;
78             Fill(recycler, mLayoutState, true, true);
79
80             FlexibleViewViewHolder nextFocus;
81             if (layoutDir == LayoutState.LAYOUT_START)
82             {
83                 nextFocus = GetChildAt(0);
84             }
85             else
86             {
87                 nextFocus = GetChildAt(ChildCount - 1);
88             }
89             return nextFocus;
90         }
91
92         private void UpdateAnchorInfoForLayout(FlexibleViewRecycler recycler, AnchorInfo anchorInfo)
93         {
94             if (UpdateAnchorFromPendingData(anchorInfo))
95             {
96                 return;
97             }
98
99             if (UpdateAnchorFromChildren(recycler, anchorInfo))
100             {
101                 return;
102             }
103
104             anchorInfo.Position = FocusPosition != NO_POSITION ? FocusPosition : 0;
105             anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
106         }
107
108
109         // If there is a pending scroll position or saved states, updates the anchor info from that
110         // data and returns true
111         private bool UpdateAnchorFromPendingData(AnchorInfo anchorInfo)
112         {
113             if (mPendingScrollPosition == NO_POSITION)
114             {
115                 return false;
116             }
117             // validate scroll position
118             if (mPendingScrollPosition < 0 || mPendingScrollPosition >= ItemCount)
119             {
120                 mPendingScrollPosition = NO_POSITION;
121                 mPendingScrollPositionOffset = INVALID_OFFSET;
122                 return false;
123             }
124
125             anchorInfo.Position = mPendingScrollPosition;
126
127             if (mPendingScrollPositionOffset == INVALID_OFFSET)
128             {
129                 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
130             }
131             else
132             {
133                 if (mShouldReverseLayout)
134                 {
135                     anchorInfo.Coordinate = mOrientationHelper.GetEndAfterPadding()
136                             - mPendingScrollPositionOffset;
137                 }
138                 else
139                 {
140                     anchorInfo.Coordinate = mOrientationHelper.GetStartAfterPadding()
141                             + mPendingScrollPositionOffset;
142                 }
143             }
144
145             return true;
146         }
147
148         // Finds an anchor child from existing Views. Most of the time, this is the view closest to
149         // start or end that has a valid position (e.g. not removed).
150         // If a child has focus, it is given priority.
151         private bool UpdateAnchorFromChildren(FlexibleViewRecycler recycler, AnchorInfo anchorInfo)
152         {
153             if (ChildCount == 0)
154             {
155                 return false;
156             }
157
158             FlexibleViewViewHolder anchorChild = FindFirstVisibleItemView();
159             if (anchorChild == null)
160             {
161                 Log.Error("flexibleview", $"exception occurs when updating anchor information!");
162                 anchorChild = GetChildAt(0);
163             }
164             anchorInfo.Position = anchorChild.LayoutPosition;
165             anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild);
166
167             return true;
168         }
169
170         // Converts a focusDirection to orientation.
171         //
172         // @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
173         //                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
174         //                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
175         //                       or 0 for not applicable
176         // @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
177         // is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
178         private int ConvertFocusDirectionToLayoutDirection(FlexibleViewLayoutManager.Direction focusDirection)
179         {
180             switch (focusDirection)
181             {
182                 case FlexibleViewLayoutManager.Direction.Up:
183                     return Orientation == VERTICAL ? LayoutState.LAYOUT_START
184                             : LayoutState.INVALID_LAYOUT;
185                 case FlexibleViewLayoutManager.Direction.Down:
186                     return Orientation == VERTICAL ? LayoutState.LAYOUT_END
187                             : LayoutState.INVALID_LAYOUT;
188                 case FlexibleViewLayoutManager.Direction.Left:
189                     return Orientation == HORIZONTAL ? LayoutState.LAYOUT_START
190                             : LayoutState.INVALID_LAYOUT;
191                 case FlexibleViewLayoutManager.Direction.Right:
192                     return Orientation == HORIZONTAL ? LayoutState.LAYOUT_END
193                             : LayoutState.INVALID_LAYOUT;
194                 default:
195                     return LayoutState.INVALID_LAYOUT;
196             }
197
198         }
199
200
201         private float Fill(FlexibleViewRecycler recycler, LayoutState layoutState, bool stopOnFocusable, bool immediate)
202         {
203             float start = layoutState.Available;
204
205             if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
206             {
207                 // TODO ugly bug fix. should not happen
208                 if (layoutState.Available < 0)
209                 {
210                     layoutState.ScrollingOffset += layoutState.Available;
211                 }
212                 if (immediate == true)
213                 {
214                     RecycleByLayoutState(recycler, layoutState, true);
215                 }
216             }
217             float remainingSpace = layoutState.Available + layoutState.Extra;
218             LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
219             while ((remainingSpace > 0) && layoutState.HasMore(ItemCount))
220             {
221                 layoutChunkResult.ResetInternal();
222                 LayoutChunk(recycler, layoutState, layoutChunkResult);
223                 if (layoutChunkResult.Finished)
224                 {
225                     break;
226                 }
227                 layoutState.Offset += layoutChunkResult.Consumed * layoutState.LayoutDirection;
228
229                 // Consume the available space if:
230                 // layoutChunk did not request to be ignored
231                 // OR we are laying out scrap children
232                 // OR we are not doing pre-layout
233                 if (!layoutChunkResult.IgnoreConsumed)
234                 {
235                     layoutState.Available -= layoutChunkResult.Consumed;
236                     // we keep a separate remaining space because mAvailable is important for recycling
237                     remainingSpace -= layoutChunkResult.Consumed;
238                 }
239
240                 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
241                 {
242                     layoutState.ScrollingOffset += layoutChunkResult.Consumed;
243                     if (layoutState.Available < 0)
244                     {
245                         layoutState.ScrollingOffset += layoutState.Available;
246                     }
247                     if (immediate == true)
248                     {
249                         RecycleByLayoutState(recycler, layoutState, true);
250                     }
251                 }
252                 if (stopOnFocusable && layoutChunkResult.Focusable)
253                 {
254                     break;
255                 }
256             }
257             if (immediate == false)
258             {
259                 RecycleByLayoutState(recycler, layoutState, false);
260             }
261
262             return start - layoutState.Available;
263         }
264
265         private void Cache(FlexibleViewRecycler recycler, LayoutState layoutState, bool immediate, float scrolled = 0)
266         {
267             if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
268             {
269                 // get the first child in the direction we are going
270                 FlexibleViewViewHolder child = GetChildClosestToEnd();
271                 if (child != null)
272                 {
273                     if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderEnd(child) + scrolled < mOrientationHelper.GetEnd())
274                     {
275                         layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
276                         layoutState.Extra = 0;
277                         layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
278                         layoutState.Recycle = false;
279                         Fill(recycler, layoutState, true, immediate);
280                     }
281                 }
282             }
283             else
284             {
285                 FlexibleViewViewHolder child = GetChildClosestToStart();
286                 if (child != null)
287                 {
288                     if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderStart(child) + scrolled > 0)
289                     {
290                         layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
291                         layoutState.Extra = 0;
292                         layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
293                         layoutState.Recycle = false;
294                         Fill(recycler, layoutState, true, immediate);
295                     }
296                 }
297             }
298         }
299
300         private void RecycleByLayoutState(FlexibleViewRecycler recycler, LayoutState layoutState, bool immediate)
301         {
302             if (!layoutState.Recycle)
303             {
304                 return;
305             }
306             if (layoutState.LayoutDirection == LayoutState.LAYOUT_START)
307             {
308                 RecycleViewsFromEnd(recycler, layoutState.ScrollingOffset, immediate);
309             }
310             else
311             {
312                 RecycleViewsFromStart(recycler, layoutState.ScrollingOffset, immediate);
313             }
314         }
315
316         private void RecycleViewsFromStart(FlexibleViewRecycler recycler, float dt, bool immediate)
317         {
318             if (dt < 0)
319             {
320                 return;
321             }
322             // ignore padding, ViewGroup may not clip children.
323             float limit = dt;
324             int childCount = ChildCount;
325             if (mShouldReverseLayout)
326             {
327                 for (int i = childCount - 1; i >= 0; i--)
328                 {
329                     FlexibleViewViewHolder child = GetChildAt(i);
330                     if (mOrientationHelper.GetViewHolderEnd(child) > limit)
331                     {
332                         // stop here
333                         RecycleChildren(recycler, childCount - 1, i, immediate);
334                         return;
335                     }
336                 }
337             }
338             else
339             {
340                 for (int i = 0; i < childCount; i++)
341                 {
342                     FlexibleViewViewHolder child = GetChildAt(i);
343                     if (mOrientationHelper.GetViewHolderEnd(child) > limit)
344                     {
345                         // stop here
346                         RecycleChildren(recycler, 0, i, immediate);
347                         return;
348                     }
349                 }
350             }
351         }
352
353         private void RecycleViewsFromEnd(FlexibleViewRecycler recycler, float dt, bool immediate)
354         {
355             if (dt < 0)
356             {
357                 return;
358             }
359             int childCount = ChildCount;
360             float limit = mOrientationHelper.GetEnd() - dt;
361             if (mShouldReverseLayout)
362             {
363                 for (int i = 0; i < childCount; i++)
364                 {
365                     FlexibleViewViewHolder child = GetChildAt(i);
366                     if (mOrientationHelper.GetViewHolderStart(child) < limit)
367                     {
368                         // stop here
369                         RecycleChildren(recycler, 0, i, immediate);
370                         return;
371                     }
372                 }
373             }
374             else
375             {
376                 for (int i = childCount - 1; i >= 0; i--)
377                 {
378                     FlexibleViewViewHolder child = GetChildAt(i);
379                     if (mOrientationHelper.GetViewHolderStart(child) < limit)
380                     {
381                         // stop here
382                         RecycleChildren(recycler, childCount - 1, i, immediate);
383                         return;
384                     }
385                 }
386             }
387         }
388
389         private float ScrollBy(float dy, FlexibleViewRecycler recycler, bool immediate)
390         {
391             if (ChildCount == 0 || dy == 0)
392             {
393                 return 0;
394             }
395             mLayoutState.Recycle = true;
396             int layoutDirection = dy < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
397             float absDy = Math.Abs(dy);
398
399             UpdateLayoutState(layoutDirection, absDy, true);
400
401             float consumed = mLayoutState.ScrollingOffset
402                 + Fill(recycler, mLayoutState, false, immediate);
403
404             if (consumed < 0)
405             {
406                 return 0;
407             }
408
409             float scrolled = absDy > consumed ? -layoutDirection * consumed : dy;
410             Cache(recycler, mLayoutState, immediate, scrolled);
411
412             mOrientationHelper.OffsetChildren(scrolled, immediate);
413
414             return scrolled;
415         }
416
417         private void UpdateLayoutState(int layoutDirection, float requiredSpace, bool canUseExistingSpace)
418         {
419             mLayoutState.Extra = 0;
420             mLayoutState.LayoutDirection = layoutDirection;
421             float scrollingOffset = 0.0f;
422             if (layoutDirection == LayoutState.LAYOUT_END)
423             {
424                 mLayoutState.Extra += mOrientationHelper.GetEndPadding();
425                 // get the first child in the direction we are going
426                 FlexibleViewViewHolder child = GetChildClosestToEnd();
427                 if (child != null)
428                 {
429                     // the direction in which we are traversing children
430                     mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
431                             : LayoutState.ITEM_DIRECTION_TAIL;
432                     mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
433                     mLayoutState.Offset = mOrientationHelper.GetViewHolderEnd(child);
434                     // calculate how much we can scroll without adding new children (independent of layout)
435                     scrollingOffset = mOrientationHelper.GetViewHolderEnd(child)
436                             - mOrientationHelper.GetEndAfterPadding();
437                 }
438
439             }
440             else
441             {
442                 mLayoutState.Extra += mOrientationHelper.GetStartAfterPadding();
443                 FlexibleViewViewHolder child = GetChildClosestToStart();
444                 if (child != null)
445                 {
446                     mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
447                             : LayoutState.ITEM_DIRECTION_HEAD;
448                     mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
449                     mLayoutState.Offset = mOrientationHelper.GetViewHolderStart(child);
450                     scrollingOffset = -mOrientationHelper.GetViewHolderStart(child)
451                             + mOrientationHelper.GetStartAfterPadding();
452                 }
453             }
454             mLayoutState.Available = requiredSpace;
455             if (canUseExistingSpace)
456             {
457                 mLayoutState.Available -= scrollingOffset;
458             }
459             mLayoutState.ScrollingOffset = scrollingOffset;
460         }
461
462         // Convenience method to find the child closes to start. Caller should check it has enough
463         // children.
464         //
465         // @return The child closes to start of the layout from user's perspective.
466         private FlexibleViewViewHolder GetChildClosestToStart()
467         {
468             return GetChildAt(mShouldReverseLayout ? ChildCount - 1 : 0);
469         }
470
471         // Convenience method to find the child closes to end. Caller should check it has enough
472         // children.
473         //
474         // @return The child closes to end of the layout from user's perspective.
475         private FlexibleViewViewHolder GetChildClosestToEnd()
476         {
477             return GetChildAt(mShouldReverseLayout ? 0 : ChildCount - 1);
478         }
479
480         private void UpdateLayoutStateToFillEnd(int itemPosition, float offset)
481         {
482             mLayoutState.Available = mOrientationHelper.GetEndAfterPadding() - offset;
483             mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
484                     LayoutState.ITEM_DIRECTION_TAIL;
485             mLayoutState.CurrentPosition = itemPosition;
486             mLayoutState.LayoutDirection = LayoutState.LAYOUT_END;
487             mLayoutState.Offset = offset;
488             mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
489             mLayoutState.Extra = mOrientationHelper.GetEndPadding();
490
491         }
492
493         private void UpdateLayoutStateToFillStart(int itemPosition, float offset)
494         {
495             mLayoutState.Available = offset - mOrientationHelper.GetStartAfterPadding();
496             mLayoutState.CurrentPosition = itemPosition;
497             mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
498                     LayoutState.ITEM_DIRECTION_HEAD;
499             mLayoutState.LayoutDirection = LayoutState.LAYOUT_START;
500             mLayoutState.Offset = offset;
501             mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
502             mLayoutState.Extra = mOrientationHelper.GetStartAfterPadding();
503         }
504
505         private FlexibleViewViewHolder FindFirstCompleteVisibleItemView()
506         {
507             int childCount = ChildCount;
508             if (mShouldReverseLayout == false)
509             {
510                 for (int i = 0; i < childCount; i++)
511                 {
512                     FlexibleViewViewHolder child = GetChildAt(i);
513                     int start = (int)mOrientationHelper.GetViewHolderStart(child);
514                     if (start > 0 && start < (int)mOrientationHelper.GetEnd())
515                     {
516                         return child;
517                     }
518                 }
519             }
520             else
521             {
522                 for (int i = childCount - 1; i >= 0; i--)
523                 {
524                     FlexibleViewViewHolder child = GetChildAt(i);
525                     int start = (int)mOrientationHelper.GetViewHolderStart(child);
526                     if (start > 0 && start < (int)mOrientationHelper.GetEnd())
527                     {
528                         return child;
529                     }
530                 }
531             }
532             return null;
533         }
534
535         private FlexibleViewViewHolder FindLastCompleteVisibleItemView()
536         {
537             int childCount = ChildCount;
538             if (mShouldReverseLayout == false)
539             {
540                 for (int i = childCount - 1; i >= 0; i--)
541                 {
542                     FlexibleViewViewHolder child = GetChildAt(i);
543                     if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
544                     {
545                         return child;
546                     }
547                 }
548             }
549             else
550             {
551                 for (int i = 0; i < childCount; i++)
552                 {
553                     FlexibleViewViewHolder child = GetChildAt(i);
554                     if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
555                     {
556                         return child;
557                     }
558                 }
559             }
560             return null;
561         }
562
563         // Helper class that keeps temporary state while {LayoutManager} is filling out the empty space.
564         internal class LayoutState
565         {
566             public const int LAYOUT_START = -1;
567
568             public const int LAYOUT_END = 1;
569
570             public const int INVALID_LAYOUT = -1000;
571
572             public const int ITEM_DIRECTION_HEAD = -1;
573
574             public const int ITEM_DIRECTION_TAIL = 1;
575
576             public const int SCROLLING_OFFSET_NaN = -10000;
577
578             // We may not want to recycle children in some cases (e.g. layout)
579             public bool Recycle = true;
580
581             // Pixel offset where layout should start
582             public float Offset;
583
584             // Number of pixels that we should fill, in the layout direction.
585             public float Available;
586
587             // Current position on the adapter to get the next item.
588             public int CurrentPosition;
589
590             // Defines the direction in which the data adapter is traversed.
591             // Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
592             public int ItemDirection;
593
594             // Defines the direction in which the layout is filled.
595             // Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
596             public int LayoutDirection;
597
598             // Used when LayoutState is constructed in a scrolling state.
599             // It should be set the amount of scrolling we can make without creating a new view.
600             // Settings this is required for efficient view recycling.
601             public float ScrollingOffset;
602
603             // Used if you want to pre-layout items that are not yet visible.
604             // The difference with {@link #mAvailable} is that, when recycling, distance laid out for
605             // {@link #mExtra} is not considered to avoid recycling visible children.
606             public float Extra = 0;
607
608             // @return true if there are more items in the data adapter
609             public bool HasMore(int itemCount)
610             {
611                 return CurrentPosition >= 0 && CurrentPosition < itemCount;
612             }
613
614             // Gets the view for the next element that we should layout.
615             // Also updates current item index to the next item, based on {@link #mItemDirection}
616             //
617             // @return The next element that we should layout.
618             public FlexibleViewViewHolder Next(FlexibleViewRecycler recycler)
619             {
620                 FlexibleViewViewHolder itemView = recycler.GetViewForPosition(CurrentPosition);
621                 CurrentPosition += ItemDirection;
622
623                 return itemView;
624             }
625         }
626
627         internal class LayoutChunkResult
628         {
629             public float Consumed;
630             public bool Finished;
631             public bool IgnoreConsumed;
632             public bool Focusable;
633
634             public void ResetInternal()
635             {
636                 Consumed = 0;
637                 Finished = false;
638                 IgnoreConsumed = false;
639                 Focusable = false;
640             }
641         }
642
643         internal class AnchorInfo
644         {
645             public int Position;
646             public float Coordinate;
647             public bool LayoutFromEnd;
648             public bool Valid;
649
650             public void Reset()
651             {
652                 Position = NO_POSITION;
653                 Coordinate = INVALID_OFFSET;
654                 LayoutFromEnd = false;
655                 Valid = false;
656             }
657         }
658     }
659 }