[NUI] Sync with dalihub (#969)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.CommonUI / Controls / FlexibleView / LinearLayoutManager.cs
1 /*
2  * Copyright(c) 2019 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 using System;
18 using System.ComponentModel;
19
20 namespace Tizen.NUI.CommonUI
21 {
22     /// <summary>
23     /// Layout collection of views horizontally/vertically.
24     /// </summary>
25     /// <since_tizen> 6 </since_tizen>
26     /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
27     [EditorBrowsable(EditorBrowsableState.Never)]
28     public class LinearLayoutManager : FlexibleView.LayoutManager
29     {
30         /// <summary>
31         /// Constant value: 0.
32         /// </summary>
33         /// <since_tizen> 6 </since_tizen>
34         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
35         [EditorBrowsable(EditorBrowsableState.Never)]
36         public static readonly int HORIZONTAL = OrientationHelper.HORIZONTAL;
37         /// <summary>
38         /// Constant value: 1.
39         /// </summary>
40         /// <since_tizen> 6 </since_tizen>
41         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
42         [EditorBrowsable(EditorBrowsableState.Never)]
43         public static readonly int VERTICAL = OrientationHelper.VERTICAL;
44         /// <summary>
45         /// Constant value: -1.
46         /// </summary>
47         /// <since_tizen> 6 </since_tizen>
48         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
49         [EditorBrowsable(EditorBrowsableState.Never)]
50         public static readonly int NO_POSITION = FlexibleView.NO_POSITION;
51         /// <summary>
52         /// Constant value: -2^31.
53         /// </summary>
54         /// <since_tizen> 6 </since_tizen>
55         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
56         [EditorBrowsable(EditorBrowsableState.Never)]
57         public static readonly int INVALID_OFFSET = -2147483648;
58
59         private static readonly float MAX_SCROLL_FACTOR = 1 / 3f;
60
61         /// <summary>
62         /// Current orientation.
63         /// </summary>
64         /// <since_tizen> 6 </since_tizen>
65         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
66         [EditorBrowsable(EditorBrowsableState.Never)]
67         protected int mOrientation;
68
69         internal OrientationHelper mOrientationHelper;
70
71         private LayoutState mLayoutState;
72         private AnchorInfo mAnchorInfo = new AnchorInfo();
73
74         /**
75          * Stashed to avoid allocation, currently only used in #fill()
76          */
77         private LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult();
78
79         private bool mShouldReverseLayout = false;
80
81         /**
82          * When LayoutManager needs to scroll to a position, it sets this variable and requests a
83          * layout which will check this variable and re-layout accordingly.
84          */
85         private int mPendingScrollPosition = NO_POSITION;
86
87         /**
88          * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
89          * called.
90          */
91         private int mPendingScrollPositionOffset = INVALID_OFFSET;
92
93         /// <summary>
94         /// Creates a LinearLayoutManager with orientation.
95         /// </summary>
96         /// <param name="orientation">Layout orientation.Should be HORIZONTAL or VERTICAL</param>
97         /// <since_tizen> 6 </since_tizen>
98         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
99         [EditorBrowsable(EditorBrowsableState.Never)]
100         public LinearLayoutManager(int orientation)
101         {
102             mOrientation = orientation;
103             mOrientationHelper = OrientationHelper.CreateOrientationHelper(this, mOrientation);
104
105             mLayoutState = new LayoutState();
106             mLayoutState.Offset = mOrientationHelper.GetStartAfterPadding();
107         }
108
109         /// <summary>
110         /// Retrieves the first visible item position.
111         /// </summary>
112         /// <since_tizen> 6 </since_tizen>
113         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
114         [EditorBrowsable(EditorBrowsableState.Never)]
115         public int FirstVisibleItemPosition
116         {
117             get
118             {
119                 FlexibleView.ViewHolder child = FindFirstVisibleItemView();
120                 return child == null ? NO_POSITION : child.LayoutPosition;
121             }
122         }
123
124         /// <summary>
125         /// Retrieves the first complete visible item position.
126         /// </summary>
127         /// <since_tizen> 6 </since_tizen>
128         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
129         [EditorBrowsable(EditorBrowsableState.Never)]
130         public int FirstCompleteVisibleItemPosition
131         {
132             get
133             {
134                 FlexibleView.ViewHolder child = FindFirstCompleteVisibleItemView();
135                 return child == null ? NO_POSITION : child.LayoutPosition;
136             }
137         }
138
139         /// <summary>
140         /// Retrieves the last visible item position.
141         /// </summary>
142         /// <since_tizen> 6 </since_tizen>
143         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
144         [EditorBrowsable(EditorBrowsableState.Never)]
145         public int LastVisibleItemPosition
146         {
147             get
148             {
149                 FlexibleView.ViewHolder child = FindLastVisibleItemView();
150                 return child == null ? NO_POSITION : child.LayoutPosition;
151             }
152         }
153
154         /// <summary>
155         /// Retrieves the last complete visible item position.
156         /// </summary>
157         /// <since_tizen> 6 </since_tizen>
158         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
159         [EditorBrowsable(EditorBrowsableState.Never)]
160         public int LastCompleteVisibleItemPosition
161         {
162             get
163             {
164                 FlexibleView.ViewHolder child = FindLastCompleteVisibleItemView();
165                 return child == null ? NO_POSITION : child.LayoutPosition;
166             }
167         }
168
169         /// <summary>
170         /// Query if horizontal scrolling is currently supported. The default implementation returns false.
171         /// </summary>
172         /// <since_tizen> 6 </since_tizen>
173         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
174         [EditorBrowsable(EditorBrowsableState.Never)]
175         public override bool CanScrollHorizontally()
176         {
177             return mOrientation == HORIZONTAL;
178         }
179
180         /// <summary>
181         /// Query if vertical scrolling is currently supported. The default implementation returns false.
182         /// </summary>
183         /// <since_tizen> 6 </since_tizen>
184         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
185         [EditorBrowsable(EditorBrowsableState.Never)]
186         public override bool CanScrollVertically()
187         {
188             return mOrientation == VERTICAL;
189         }
190
191         /// <summary>
192         /// Lay out all relevant child views from the given adapter.
193         /// </summary>
194         /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
195         /// <since_tizen> 6 </since_tizen>
196         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
197         [EditorBrowsable(EditorBrowsableState.Never)]
198         public override void OnLayoutChildren(FlexibleView.Recycler recycler)
199         {
200             mLayoutState.Recycle = false;
201             if (!mAnchorInfo.Valid || mPendingScrollPosition != NO_POSITION)
202             {
203                 mAnchorInfo.Reset();
204                 mAnchorInfo.LayoutFromEnd = mShouldReverseLayout;
205                 // calculate anchor position and coordinate
206                 UpdateAnchorInfoForLayout(recycler, mAnchorInfo);
207                 mAnchorInfo.Valid = true;
208             }
209
210             int firstLayoutDirection;
211             if (mAnchorInfo.LayoutFromEnd)
212             {
213                 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
214                         : LayoutState.ITEM_DIRECTION_HEAD;
215             }
216             else
217             {
218                 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
219                         : LayoutState.ITEM_DIRECTION_TAIL;
220             }
221             EnsureAnchorReady(recycler, mAnchorInfo, firstLayoutDirection);
222             ScrapAttachedViews(recycler);
223
224             if (mAnchorInfo.LayoutFromEnd == true)
225             {
226                 UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate);
227                 Fill(recycler, mLayoutState, false, true);
228                 Cache(recycler, mLayoutState, true);
229
230                 UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate);
231                 mLayoutState.CurrentPosition += mLayoutState.ItemDirection;
232                 Fill(recycler, mLayoutState, false, true);
233                 Cache(recycler, mLayoutState, true);
234             }
235             else
236             {
237                 UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate);
238                 Fill(recycler, mLayoutState, false, true);
239                 Cache(recycler, mLayoutState, true);
240
241                 UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate);
242                 mLayoutState.CurrentPosition += mLayoutState.ItemDirection;
243                 Fill(recycler, mLayoutState, false, true);
244                 Cache(recycler, mLayoutState, true);
245             }
246
247             OnLayoutCompleted();
248         }
249
250         /// <summary>
251         /// Scroll horizontally by dy pixels in screen coordinates.
252         /// </summary>
253         /// <param name="dy">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
254         /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
255         /// <param name="immediate">Specify if the scroll need animation</param>
256         /// <since_tizen> 6 </since_tizen>
257         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
258         [EditorBrowsable(EditorBrowsableState.Never)]
259         public override float ScrollHorizontallyBy(float dx, FlexibleView.Recycler recycler, bool immediate)
260         {
261             if (mOrientation == VERTICAL)
262             {
263                 return 0;
264             }
265             return ScrollBy(dx, recycler, immediate);
266         }
267
268         /// <summary>
269         /// Scroll vertically by dy pixels in screen coordinates.
270         /// </summary>
271         /// <param name="dy">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
272         /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
273         /// <param name="immediate">Specify if the scroll need animation</param>
274         /// <since_tizen> 6 </since_tizen>
275         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
276         [EditorBrowsable(EditorBrowsableState.Never)]
277         public override float ScrollVerticallyBy(float dy, FlexibleView.Recycler recycler, bool immediate)
278         {
279             if (mOrientation == HORIZONTAL)
280             {
281                 return 0;
282             }
283             return ScrollBy(dy, recycler, immediate); ;
284         }
285
286         /// <summary>
287         /// Compute the offset of the scrollbar's thumb within the range.
288         /// </summary>
289         /// <since_tizen> 6 </since_tizen>
290         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
291         [EditorBrowsable(EditorBrowsableState.Never)]
292         public override float ComputeScrollOffset()
293         {
294             FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
295             FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
296             if (GetChildCount() == 0 || startChild == null || endChild == null)
297             {
298                 return 0;
299             }
300             int minPosition = Math.Min(startChild.LayoutPosition, endChild.LayoutPosition);
301             int maxPosition = Math.Max(startChild.LayoutPosition, endChild.LayoutPosition);
302             int itemsBefore = mShouldReverseLayout
303                     ? Math.Max(0, ItemCount - maxPosition - 1)
304                     : Math.Max(0, minPosition);
305
306             float laidOutArea = Math.Abs(mOrientationHelper.GetViewHolderEnd(endChild)
307                    - mOrientationHelper.GetViewHolderStart(startChild));
308             int itemRange = Math.Abs(startChild.LayoutPosition - endChild.LayoutPosition) + 1;
309             float avgSizePerRow = laidOutArea / itemRange;
310
311             return (float)Math.Round(itemsBefore * avgSizePerRow + (mOrientationHelper.GetStartAfterPadding()
312                     - mOrientationHelper.GetViewHolderStart(startChild)));
313         }
314
315         /// <summary>
316         /// Compute the extent of the scrollbar's thumb within the range.
317         /// </summary>
318         /// <since_tizen> 6 </since_tizen>
319         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
320         [EditorBrowsable(EditorBrowsableState.Never)]
321         public override float ComputeScrollExtent()
322         {
323             FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
324             FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
325             if (GetChildCount() == 0 || startChild == null || endChild == null)
326             {
327                 return 0;
328             }
329             float extend = mOrientationHelper.GetViewHolderEnd(endChild)
330                 - mOrientationHelper.GetViewHolderStart(startChild);
331             return Math.Min(mOrientationHelper.GetTotalSpace(), extend);
332         }
333
334         /// <summary>
335         /// Compute the range that the scrollbar represents.
336         /// </summary>
337         /// <since_tizen> 6 </since_tizen>
338         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
339         [EditorBrowsable(EditorBrowsableState.Never)]
340         public override float ComputeScrollRange()
341         {
342             FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
343             FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
344             if (GetChildCount() == 0 || startChild == null || endChild == null)
345             {
346                 return 0;
347             }
348             float laidOutArea = mOrientationHelper.GetViewHolderEnd(endChild)
349                     - mOrientationHelper.GetViewHolderStart(startChild);
350             int laidOutRange = Math.Abs(startChild.LayoutPosition - endChild.LayoutPosition) + 1;
351             // estimate a size for full list.
352             return laidOutArea / laidOutRange * ItemCount;
353         }
354
355         /// <summary>
356         /// Scroll the FlexibleView to make the position visible.
357         /// </summary>
358         /// <param name="position">Scroll to this adapter position</param>
359         /// <since_tizen> 6 </since_tizen>
360         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
361         [EditorBrowsable(EditorBrowsableState.Never)]
362         public override void ScrollToPosition(int position)
363         {
364             mPendingScrollPosition = position;
365             mPendingScrollPositionOffset = INVALID_OFFSET;
366
367             RelayoutRequest();
368         }
369
370         /// <summary>
371         /// Scroll to the specified adapter position with the given offset from resolved layout start.
372         /// </summary>
373         /// <param name="position">Scroll to this adapter position</param>
374         /// <param name="offset">The distance (in pixels) between the start edge of the item view and start edge of the FlexibleView.</param>
375         /// <since_tizen> 6 </since_tizen>
376         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
377         [EditorBrowsable(EditorBrowsableState.Never)]
378         public override void ScrollToPositionWithOffset(int position, int offset)
379         {
380             mPendingScrollPosition = position;
381             mPendingScrollPositionOffset = offset;
382
383             RelayoutRequest();
384         }
385
386         /// <summary>
387         /// Called after a full layout calculation is finished.
388         /// </summary>
389         /// <since_tizen> 6 </since_tizen>
390         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
391         [EditorBrowsable(EditorBrowsableState.Never)]
392         public override void OnLayoutCompleted()
393         {
394             if (mPendingScrollPosition != NO_POSITION)
395             {
396                 ChangeFocus(mPendingScrollPosition);
397             }
398             mPendingScrollPosition = NO_POSITION;
399             mPendingScrollPositionOffset = INVALID_OFFSET;
400
401             mAnchorInfo.Reset();
402         }
403
404         internal virtual void EnsureAnchorReady(FlexibleView.Recycler recycler, AnchorInfo anchorInfo, int itemDirection)
405         {
406
407         }
408
409
410         /// <summary>
411         /// Retrieves a position that neighbor to current position by direction.
412         /// </summary>
413         /// <param name="position">The anchor adapter position</param>
414         /// <param name="direction">The direction.</param>
415         /// <since_tizen> 6 </since_tizen>
416         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
417         [EditorBrowsable(EditorBrowsableState.Never)]
418         protected override int GetNextPosition(int position, FlexibleView.LayoutManager.Direction direction)
419         {
420             if (mOrientation == HORIZONTAL)
421             {
422                 switch (direction)
423                 {
424                     case FlexibleView.LayoutManager.Direction.Left:
425                         if (position > 0)
426                         {
427                             return position - 1;
428                         }
429                         break;
430                     case FlexibleView.LayoutManager.Direction.Right:
431                         if (position < ItemCount - 1)
432                         {
433                             return position + 1;
434                         }
435                         break;
436                 }
437             }
438             else
439             {
440                 switch (direction)
441                 {
442                     case FlexibleView.LayoutManager.Direction.Up:
443                         if (position > 0)
444                         {
445                             return position - 1;
446                         }
447                         break;
448                     case FlexibleView.LayoutManager.Direction.Down:
449                         if (position < ItemCount - 1)
450                         {
451                             return position + 1;
452                         }
453                         break;
454                 }
455             }
456
457             return NO_POSITION;
458         }
459
460         internal virtual void LayoutChunk(FlexibleView.Recycler recycler,
461             LayoutState layoutState, LayoutChunkResult result)
462         {
463             FlexibleView.ViewHolder holder = layoutState.Next(recycler);
464             if (holder == null)
465             {
466                 // if we are laying out views in scrap, this may return null which means there is
467                 // no more items to layout.
468                 result.Finished = true;
469                 return;
470             }
471
472             if (mShouldReverseLayout == (layoutState.LayoutDirection == LayoutState.LAYOUT_START))
473                 AddView(holder);
474             else
475                 AddView(holder, 0);
476
477             result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder);
478
479             float left, top, width, height;
480             if (mOrientation == VERTICAL)
481             {
482                 width = GetWidth() - GetPaddingLeft() - GetPaddingRight();
483                 height = result.Consumed;
484                 left = GetPaddingLeft();
485                 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
486                 {
487                     top = layoutState.Offset;
488                 }
489                 else
490                 {
491                     top = layoutState.Offset - height;
492                 }
493                 LayoutChild(holder, left, top, width, height);
494             }
495             else
496             {
497                 width = result.Consumed;
498                 height = GetHeight() - GetPaddingTop() - GetPaddingBottom();
499                 top = GetPaddingTop();
500                 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
501                 {
502                     left = layoutState.Offset;
503                 }
504                 else
505                 {
506                     left = layoutState.Offset - width;
507                 }
508                 LayoutChild(holder, left, top, width, height);
509             }
510
511             result.Focusable = true;
512         }
513
514         internal override FlexibleView.ViewHolder OnFocusSearchFailed(FlexibleView.ViewHolder focused, FlexibleView.LayoutManager.Direction direction, FlexibleView.Recycler recycler)
515         {
516             if (GetChildCount() == 0)
517             {
518                 return null;
519             }
520             int layoutDir = ConvertFocusDirectionToLayoutDirection(direction);
521             if (layoutDir == LayoutState.INVALID_LAYOUT)
522             {
523                 return null;
524             }
525             int maxScroll = (int)(MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace());
526             UpdateLayoutState(layoutDir, maxScroll, false);
527             mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
528             mLayoutState.Recycle = false;
529             Fill(recycler, mLayoutState, true, true);
530
531             FlexibleView.ViewHolder nextFocus;
532             if (layoutDir == LayoutState.LAYOUT_START)
533             {
534                 nextFocus = GetChildAt(0);
535             }
536             else
537             {
538                 nextFocus = GetChildAt(GetChildCount() - 1);
539             }
540             return nextFocus;
541         }
542
543
544         private void UpdateAnchorInfoForLayout(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
545         {
546             if (UpdateAnchorFromPendingData(anchorInfo))
547             {
548                 return;
549             }
550
551             if (UpdateAnchorFromChildren(recycler, anchorInfo))
552             {
553                 return;
554             }
555
556             anchorInfo.Position = FocusPosition != NO_POSITION ? FocusPosition : 0;
557             anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
558         }
559
560         /**
561          * If there is a pending scroll position or saved states, updates the anchor info from that
562          * data and returns true
563          */
564         private bool UpdateAnchorFromPendingData(AnchorInfo anchorInfo)
565         {
566             if (mPendingScrollPosition == NO_POSITION)
567             {
568                 return false;
569             }
570             // validate scroll position
571             if (mPendingScrollPosition < 0 || mPendingScrollPosition >= ItemCount)
572             {
573                 mPendingScrollPosition = NO_POSITION;
574                 mPendingScrollPositionOffset = INVALID_OFFSET;
575                 return false;
576             }
577
578             anchorInfo.Position = mPendingScrollPosition;
579
580             if (mPendingScrollPositionOffset == INVALID_OFFSET)
581             {
582                 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
583             }
584             else
585             {
586                 if (mShouldReverseLayout)
587                 {
588                     anchorInfo.Coordinate = mOrientationHelper.GetEndAfterPadding()
589                             - mPendingScrollPositionOffset;
590                 }
591                 else
592                 {
593                     anchorInfo.Coordinate = mOrientationHelper.GetStartAfterPadding()
594                             + mPendingScrollPositionOffset;
595                 }
596             }
597             return true;
598         }
599
600         /**
601          * Finds an anchor child from existing Views. Most of the time, this is the view closest to
602          * start or end that has a valid position (e.g. not removed).
603          * If a child has focus, it is given priority.
604          */
605         private bool UpdateAnchorFromChildren(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
606         {
607             if (GetChildCount() == 0)
608             {
609                 return false;
610             }
611
612             FlexibleView.ViewHolder anchorChild = FindFirstCompleteVisibleItemView();
613             if (anchorChild != null)
614             {
615                 return false;
616             }
617             anchorInfo.Position = anchorChild.LayoutPosition;
618             anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild);
619
620             return true;
621         }
622
623         /**
624          * Converts a focusDirection to orientation.
625          *
626          * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
627          *                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
628          *                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
629          *                       or 0 for not applicable
630          * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
631          * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
632          */
633         private int ConvertFocusDirectionToLayoutDirection(FlexibleView.LayoutManager.Direction focusDirection)
634         {
635             switch (focusDirection)
636             {
637                 case FlexibleView.LayoutManager.Direction.Up:
638                     return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
639                             : LayoutState.INVALID_LAYOUT;
640                 case FlexibleView.LayoutManager.Direction.Down:
641                     return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
642                             : LayoutState.INVALID_LAYOUT;
643                 case FlexibleView.LayoutManager.Direction.Left:
644                     return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
645                             : LayoutState.INVALID_LAYOUT;
646                 case FlexibleView.LayoutManager.Direction.Right:
647                     return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
648                             : LayoutState.INVALID_LAYOUT;
649                 default:
650                     return LayoutState.INVALID_LAYOUT;
651             }
652
653         }
654
655
656         private float Fill(FlexibleView.Recycler recycler, LayoutState layoutState, bool stopOnFocusable, bool immediate)
657         {
658             float start = layoutState.Available;
659             if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
660             {
661                 // TODO ugly bug fix. should not happen
662                 if (layoutState.Available < 0)
663                 {
664                     layoutState.ScrollingOffset += layoutState.Available;
665                 }
666                 if (immediate == true)
667                 {
668                     RecycleByLayoutState(recycler, layoutState, true);
669                 }
670             }
671             float remainingSpace = layoutState.Available + layoutState.Extra;
672             LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
673             while ((remainingSpace > 0) && layoutState.HasMore(ItemCount))
674             {
675                 layoutChunkResult.ResetInternal();
676                 LayoutChunk(recycler, layoutState, layoutChunkResult);
677                 if (layoutChunkResult.Finished)
678                 {
679                     break;
680                 }
681                 layoutState.Offset += layoutChunkResult.Consumed * layoutState.LayoutDirection;
682                 /**
683                  * Consume the available space if:
684                  * layoutChunk did not request to be ignored
685                  * OR we are laying out scrap children
686                  * OR we are not doing pre-layout
687                  */
688                 if (!layoutChunkResult.IgnoreConsumed)
689                 {
690                     layoutState.Available -= layoutChunkResult.Consumed;
691                     // we keep a separate remaining space because mAvailable is important for recycling
692                     remainingSpace -= layoutChunkResult.Consumed;
693                 }
694
695                 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
696                 {
697                     layoutState.ScrollingOffset += layoutChunkResult.Consumed;
698                     if (layoutState.Available < 0)
699                     {
700                         layoutState.ScrollingOffset += layoutState.Available;
701                     }
702                     if (immediate == true)
703                     {
704                         RecycleByLayoutState(recycler, layoutState, true);
705                     }
706                 }
707                 if (stopOnFocusable && layoutChunkResult.Focusable)
708                 {
709                     break;
710                 }
711             }
712             if (immediate == false)
713             {
714                 RecycleByLayoutState(recycler, layoutState, false);
715             }
716
717             return start - layoutState.Available;
718         }
719
720         private void Cache(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate, float scrolled = 0)
721         {
722             if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
723             {
724                 // get the first child in the direction we are going
725                 FlexibleView.ViewHolder child = GetChildClosestToEnd();
726                 //Log.Fatal("TV.FLUX.Component", $"==========> child:{child.LayoutGroupIndex}-{child.LayoutItemIndex} childEnd:{orientationHelper.GetItemEnd(child)} # {orientationHelper.GetEnd()}");
727
728                 if (child != null)
729                 {
730                     if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderEnd(child) + scrolled < mOrientationHelper.GetEnd())
731                     {
732                         layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
733                         layoutState.Extra = 0;
734                         layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
735                         layoutState.Recycle = false;
736                         Fill(recycler, layoutState, true, immediate);
737                     }
738                 }
739             }
740             else
741             {
742                 FlexibleView.ViewHolder child = GetChildClosestToStart();
743
744                 if (child != null)
745                 {
746                     if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderStart(child) + scrolled > 0)
747                     {
748                         layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
749                         layoutState.Extra = 0;
750                         layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
751                         layoutState.Recycle = false;
752                         Fill(recycler, layoutState, true, immediate);
753                     }
754                 }
755             }
756         }
757
758         private void RecycleByLayoutState(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate)
759         {
760             if (!layoutState.Recycle)
761             {
762                 return;
763             }
764             if (layoutState.LayoutDirection == LayoutState.LAYOUT_START)
765             {
766                 RecycleViewsFromEnd(recycler, layoutState.ScrollingOffset, immediate);
767             }
768             else
769             {
770                 RecycleViewsFromStart(recycler, layoutState.ScrollingOffset, immediate);
771             }
772         }
773
774         private void RecycleViewsFromStart(FlexibleView.Recycler recycler, float dt, bool immediate)
775         {
776             if (dt < 0)
777             {
778                 return;
779             }
780             // ignore padding, ViewGroup may not clip children.
781             float limit = dt;
782             int childCount = GetChildCount();
783             if (mShouldReverseLayout)
784             {
785                 for (int i = childCount - 1; i >= 0; i--)
786                 {
787                     FlexibleView.ViewHolder child = GetChildAt(i);
788                     if (mOrientationHelper.GetViewHolderEnd(child) > limit)
789                     {
790                         // stop here
791                         RecycleChildren(recycler, childCount - 1, i, immediate);
792                         return;
793                     }
794                 }
795             }
796             else
797             {
798                 for (int i = 0; i < childCount; i++)
799                 {
800                     FlexibleView.ViewHolder child = GetChildAt(i);
801                     if (mOrientationHelper.GetViewHolderEnd(child) > limit)
802                     {
803                         // stop here
804                         RecycleChildren(recycler, 0, i, immediate);
805                         return;
806                     }
807                 }
808             }
809         }
810
811         private void RecycleViewsFromEnd(FlexibleView.Recycler recycler, float dt, bool immediate)
812         {
813             int childCount = GetChildCount();
814             if (dt < 0)
815             {
816                 return;
817             }
818             float limit = mOrientationHelper.GetEnd() - dt;
819             if (mShouldReverseLayout)
820             {
821                 for (int i = 0; i < childCount; i++)
822                 {
823                     FlexibleView.ViewHolder child = GetChildAt(i);
824                     if (mOrientationHelper.GetViewHolderStart(child) < limit)
825                     {
826                         // stop here
827                         RecycleChildren(recycler, 0, i, immediate);
828                         return;
829                     }
830                 }
831             }
832             else
833             {
834                 for (int i = childCount - 1; i >= 0; i--)
835                 {
836                     FlexibleView.ViewHolder child = GetChildAt(i);
837                     if (mOrientationHelper.GetViewHolderStart(child) < limit)
838                     {
839                         // stop here
840                         RecycleChildren(recycler, childCount - 1, i, immediate);
841                         return;
842                     }
843                 }
844             }
845         }
846
847         private float ScrollBy(float dy, FlexibleView.Recycler recycler, bool immediate)
848         {
849             if (GetChildCount() == 0 || dy == 0)
850             {
851                 return 0;
852             }
853             mLayoutState.Recycle = true;
854             int layoutDirection = dy < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
855             float absDy = Math.Abs(dy);
856             UpdateLayoutState(layoutDirection, absDy, true);
857             float consumed = mLayoutState.ScrollingOffset
858                 + Fill(recycler, mLayoutState, false, immediate);
859
860             if (consumed < 0)
861             {
862                 return 0;
863             }
864
865             float scrolled = absDy > consumed ? -layoutDirection * consumed : dy;
866
867             Cache(recycler, mLayoutState, immediate, scrolled);
868
869             mOrientationHelper.OffsetChildren(scrolled, immediate);
870
871
872             return scrolled;
873         }
874
875         private void UpdateLayoutState(int layoutDirection, float requiredSpace, bool canUseExistingSpace)
876         {
877             mLayoutState.Extra = 0;
878             mLayoutState.LayoutDirection = layoutDirection;
879             float scrollingOffset = 0.0f;
880             if (layoutDirection == LayoutState.LAYOUT_END)
881             {
882                 mLayoutState.Extra += mOrientationHelper.GetEndPadding();
883                 // get the first child in the direction we are going
884                 FlexibleView.ViewHolder child = GetChildClosestToEnd();
885                 if (child != null)
886                 {
887                     // the direction in which we are traversing children
888                     mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
889                             : LayoutState.ITEM_DIRECTION_TAIL;
890                     mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
891                     mLayoutState.Offset = mOrientationHelper.GetViewHolderEnd(child);
892                     // calculate how much we can scroll without adding new children (independent of layout)
893                     scrollingOffset = mOrientationHelper.GetViewHolderEnd(child)
894                             - mOrientationHelper.GetEndAfterPadding();
895                 }
896
897             }
898             else
899             {
900                 mLayoutState.Extra += mOrientationHelper.GetStartAfterPadding();
901                 FlexibleView.ViewHolder child = GetChildClosestToStart();
902                 if (child != null)
903                 {
904                    mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
905                            : LayoutState.ITEM_DIRECTION_HEAD;
906                    mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
907                    mLayoutState.Offset = mOrientationHelper.GetViewHolderStart(child);
908                    scrollingOffset = -mOrientationHelper.GetViewHolderStart(child)
909                            + mOrientationHelper.GetStartAfterPadding();
910                 }
911             }
912             mLayoutState.Available = requiredSpace;
913             if (canUseExistingSpace)
914             {
915                 mLayoutState.Available -= scrollingOffset;
916             }
917             mLayoutState.ScrollingOffset = scrollingOffset;
918
919         }
920         /**
921          * Convenience method to find the child closes to start. Caller should check it has enough
922          * children.
923          *
924          * @return The child closes to start of the layout from user's perspective.
925          */
926         private FlexibleView.ViewHolder GetChildClosestToStart()
927         {
928             return GetChildAt(mShouldReverseLayout ? GetChildCount() - 1 : 0);
929         }
930
931         /**
932          * Convenience method to find the child closes to end. Caller should check it has enough
933          * children.
934          *
935          * @return The child closes to end of the layout from user's perspective.
936          */
937         private FlexibleView.ViewHolder GetChildClosestToEnd()
938         {
939             return GetChildAt(mShouldReverseLayout ? 0 : GetChildCount() - 1);
940         }
941
942         private void UpdateLayoutStateToFillEnd(int itemPosition, float offset)
943         {
944             mLayoutState.Available = mOrientationHelper.GetEndAfterPadding() - offset;
945             mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
946                     LayoutState.ITEM_DIRECTION_TAIL;
947             mLayoutState.CurrentPosition = itemPosition;
948             mLayoutState.LayoutDirection = LayoutState.LAYOUT_END;
949             mLayoutState.Offset = offset;
950             mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
951             mLayoutState.Extra = mOrientationHelper.GetEndPadding();
952         }
953
954         private void UpdateLayoutStateToFillStart(int itemPosition, float offset)
955         {
956             mLayoutState.Available = offset - mOrientationHelper.GetStartAfterPadding();
957             mLayoutState.CurrentPosition = itemPosition;
958             mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
959                     LayoutState.ITEM_DIRECTION_HEAD;
960             mLayoutState.LayoutDirection = LayoutState.LAYOUT_START;
961             mLayoutState.Offset = offset;
962             mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
963             mLayoutState.Extra = mOrientationHelper.GetStartAfterPadding();
964         }
965
966         private FlexibleView.ViewHolder FindFirstVisibleItemView()
967         {
968             int childCount = GetChildCount();
969             if (mShouldReverseLayout == false)
970             {
971                 for (int i = 0; i < childCount; i++)
972                 {
973                     FlexibleView.ViewHolder child = GetChildAt(i);
974                     if ((int)mOrientationHelper.GetViewHolderEnd(child) > 0)
975                     {
976                         return child;
977                     }
978                 }
979             }
980             else
981             {
982                 for (int i = childCount - 1; i >= 0; i--)
983                 {
984                     FlexibleView.ViewHolder child = GetChildAt(i);
985                     if ((int)mOrientationHelper.GetViewHolderEnd(child) > 0)
986                     {
987                         return child;
988                     }
989                 }
990             }
991             return null;
992         }
993
994         private FlexibleView.ViewHolder FindFirstCompleteVisibleItemView()
995         {
996             int childCount = GetChildCount();
997             if (mShouldReverseLayout == false)
998             {
999                 for (int i = 0; i < childCount; i++)
1000                 {
1001                     FlexibleView.ViewHolder child = GetChildAt(i);
1002                     if ((int)mOrientationHelper.GetViewHolderStart(child) > 0)
1003                     {
1004                         return child;
1005                     }
1006                 }
1007             }
1008             else
1009             {
1010                 for (int i = childCount - 1; i >= 0; i--)
1011                 {
1012                     FlexibleView.ViewHolder child = GetChildAt(i);
1013                     if ((int)mOrientationHelper.GetViewHolderStart(child) > 0)
1014                     {
1015                         return child;
1016                     }
1017                 }
1018             }
1019             return null;
1020         }
1021
1022         private FlexibleView.ViewHolder FindLastVisibleItemView()
1023         {
1024             int childCount = GetChildCount();
1025             if (mShouldReverseLayout == false)
1026             {
1027                 for (int i = childCount - 1; i >= 0; i--)
1028                 {
1029                     FlexibleView.ViewHolder child = GetChildAt(i);
1030                     if ((int)mOrientationHelper.GetViewHolderStart(child) < (int)mOrientationHelper.GetEnd())
1031                     {
1032                         return child;
1033                     }
1034                 }
1035             }
1036             else
1037             {
1038                 for (int i = 0; i < childCount; i++)
1039                 {
1040                     FlexibleView.ViewHolder child = GetChildAt(i);
1041                     if ((int)mOrientationHelper.GetViewHolderStart(child) < (int)mOrientationHelper.GetEnd())
1042                     {
1043                         return child;
1044                     }
1045                 }
1046             }
1047             return null;
1048         }
1049
1050         private FlexibleView.ViewHolder FindLastCompleteVisibleItemView()
1051         {
1052             int childCount = GetChildCount();
1053             if (mShouldReverseLayout == false)
1054             {
1055                 for (int i = childCount - 1; i >= 0; i--)
1056                 {
1057                     FlexibleView.ViewHolder child = GetChildAt(i);
1058                     if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1059                     {
1060                         return child;
1061                     }
1062                 }
1063             }
1064             else
1065             {
1066                 for (int i = 0; i < childCount; i++)
1067                 {
1068                     FlexibleView.ViewHolder child = GetChildAt(i);
1069                     if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1070                     {
1071                         return child;
1072                     }
1073                 }
1074             }
1075             return null;
1076         }
1077
1078
1079         /**
1080          * Helper class that keeps temporary state while {LayoutManager} is filling out the empty space.
1081          **/
1082         internal class LayoutState
1083         {
1084             public static readonly int LAYOUT_START = -1;
1085
1086             public static readonly int LAYOUT_END = 1;
1087
1088             public static readonly int INVALID_LAYOUT = -1000;
1089
1090             public static readonly int ITEM_DIRECTION_HEAD = -1;
1091
1092             public static readonly int ITEM_DIRECTION_TAIL = 1;
1093
1094             public static readonly int SCROLLING_OFFSET_NaN = -10000;
1095
1096             /**
1097              * We may not want to recycle children in some cases (e.g. layout)
1098              */
1099             public bool Recycle = true;
1100
1101             /**
1102              * Pixel offset where layout should start
1103              */
1104             public float Offset;
1105
1106             /**
1107              * Number of pixels that we should fill, in the layout direction.
1108              */
1109             public float Available;
1110
1111             /**
1112              * Current position on the adapter to get the next item.
1113              */
1114             public int CurrentPosition;
1115
1116             /**
1117              * Defines the direction in which the data adapter is traversed.
1118              * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
1119              */
1120             public int ItemDirection;
1121
1122             /**
1123              * Defines the direction in which the layout is filled.
1124              * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
1125              */
1126             public int LayoutDirection;
1127
1128             /**
1129              * Used when LayoutState is constructed in a scrolling state.
1130              * It should be set the amount of scrolling we can make without creating a new view.
1131              * Settings this is required for efficient view recycling.
1132              */
1133             public float ScrollingOffset;
1134
1135             /**
1136              * Used if you want to pre-layout items that are not yet visible.
1137              * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
1138              * {@link #mExtra} is not considered to avoid recycling visible children.
1139              */
1140             public float Extra = 0;
1141
1142
1143             /**
1144              * @return true if there are more items in the data adapter
1145              */
1146             public bool HasMore(int itemCount)
1147             {
1148                 return CurrentPosition >= 0 && CurrentPosition < itemCount;
1149             }
1150
1151             /**
1152          * Gets the view for the next element that we should layout.
1153          * Also updates current item index to the next item, based on {@link #mItemDirection}
1154          *
1155          * @return The next element that we should layout.
1156          */
1157             public FlexibleView.ViewHolder Next(FlexibleView.Recycler recycler)
1158             {
1159                 FlexibleView.ViewHolder itemView = recycler.GetViewForPosition(CurrentPosition);
1160                 CurrentPosition += ItemDirection;
1161                 return itemView;
1162             }
1163         }
1164
1165         internal class LayoutChunkResult
1166         {
1167             public float Consumed;
1168             public bool Finished;
1169             public bool IgnoreConsumed;
1170             public bool Focusable;
1171
1172             public void ResetInternal()
1173             {
1174                 Consumed = 0;
1175                 Finished = false;
1176                 IgnoreConsumed = false;
1177                 Focusable = false;
1178             }
1179         }
1180
1181         internal class AnchorInfo
1182         {
1183             public int Position;
1184             public float Coordinate;
1185             public bool LayoutFromEnd;
1186             public bool Valid;
1187
1188             public void Reset()
1189             {
1190                 Position = NO_POSITION;
1191                 Coordinate = INVALID_OFFSET;
1192                 LayoutFromEnd = false;
1193                 Valid = false;
1194             }
1195
1196         }
1197     }
1198 }