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