1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
21 namespace Tizen.NUI.Components
24 /// layouter for CollectionView to display items in linear layout.
26 /// <since_tizen> 9 </since_tizen>
27 public class LinearLayouter : ItemsLayouter
29 private readonly List<float> ItemPosition = new List<float>();
30 private readonly List<float> ItemSize = new List<float>();
31 private int ItemSizeChanged = -1;
32 private CollectionView colView;
33 private bool hasHeader;
34 private float headerSize;
35 private Extents headerMargin;
36 private bool hasFooter;
37 private float footerSize;
38 private Extents footerMargin;
39 private bool isGrouped;
40 private readonly List<GroupInfo> groups = new List<GroupInfo>();
41 private float groupHeaderSize;
42 private Extents groupHeaderMargin;
43 private float groupFooterSize;
44 private Extents groupFooterMargin;
45 private GroupInfo Visited;
46 private Timer requestLayoutTimer = null;
47 private bool isSourceEmpty;
50 /// Clean up ItemsLayouter.
52 /// <param name="view"> CollectionView of layouter.</param>
53 /// <remarks>please note that, view must be type of CollectionView</remarks>
54 /// <since_tizen> 9 </since_tizen>
55 public override void Initialize(RecyclerView view)
57 colView = view as CollectionView;
60 throw new ArgumentException("LinearLayouter only can be applied CollectionView.", nameof(view));
68 IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
70 RecyclerViewItem header = colView?.Header;
71 RecyclerViewItem footer = colView?.Footer;
73 int count = colView.InternalItemSource.Count;
77 MeasureChild(colView, header);
79 width = header.Layout != null? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
80 height = header.Layout != null? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
82 Extents itemMargin = header.Margin;
83 headerSize = IsHorizontal?
84 width + itemMargin.Start + itemMargin.End:
85 height + itemMargin.Top + itemMargin.Bottom;
86 headerMargin = new Extents(itemMargin);
89 colView.UnrealizeItem(header);
91 else hasHeader = false;
95 MeasureChild(colView, footer);
97 width = footer.Layout != null? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
98 height = footer.Layout != null? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
100 Extents itemMargin = footer.Margin;
101 footerSize = IsHorizontal?
102 width + itemMargin.Start + itemMargin.End:
103 height + itemMargin.Top + itemMargin.Bottom;
104 footerMargin = new Extents(itemMargin);
105 footer.Index = count - 1;
108 colView.UnrealizeItem(footer);
110 else hasFooter = false;
112 //No Internal Source exist.
113 if (count == (hasHeader? (hasFooter? 2 : 1) : 0))
115 isSourceEmpty = true;
118 isSourceEmpty = false;
120 int firstIndex = hasHeader? 1 : 0;
122 if (colView.IsGrouped)
126 if (colView.GroupHeaderTemplate != null)
128 while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
129 //must be always true
130 if (colView.InternalItemSource.IsGroupHeader(firstIndex))
132 RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
135 if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
137 // Need to Set proper height or width on scroll direction.
138 if (groupHeader.Layout == null)
140 width = groupHeader.WidthSpecification;
141 height = groupHeader.HeightSpecification;
145 MeasureChild(colView, groupHeader);
147 width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
148 height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
150 // pick the StepCandidate.
151 Extents itemMargin = groupHeader.Margin;
152 groupHeaderSize = IsHorizontal?
153 width + itemMargin.Start + itemMargin.End:
154 height + itemMargin.Top + itemMargin.Bottom;
155 groupHeaderMargin = new Extents(itemMargin);
156 colView.UnrealizeItem(groupHeader);
161 groupHeaderSize = 0F;
164 if (colView.GroupFooterTemplate != null)
166 int firstFooter = firstIndex;
167 while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
168 //must be always true
169 if (colView.InternalItemSource.IsGroupFooter(firstFooter))
171 RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
173 if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
174 // Need to Set proper height or width on scroll direction.
175 if (groupFooter.Layout == null)
177 width = groupFooter.WidthSpecification;
178 height = groupFooter.HeightSpecification;
182 MeasureChild(colView, groupFooter);
184 width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
185 height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
187 // pick the StepCandidate.
188 Extents itemMargin = groupFooter.Margin;
189 groupFooterSize = IsHorizontal?
190 width + itemMargin.Start + itemMargin.End:
191 height + itemMargin.Top + itemMargin.Bottom;
192 groupFooterMargin = new Extents(itemMargin);
193 colView.UnrealizeItem(groupFooter);
198 groupFooterSize = 0F;
201 else isGrouped = false;
204 //Final Check of FirstIndex
205 while (colView.InternalItemSource.IsHeader(firstIndex) ||
206 colView.InternalItemSource.IsGroupHeader(firstIndex) ||
207 colView.InternalItemSource.IsGroupFooter(firstIndex))
209 if (colView.InternalItemSource.IsFooter(firstIndex))
220 RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
221 if (sizeDeligate == null)
224 throw new Exception("Cannot create content from DatTemplate.");
227 sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
229 // Need to Set proper height or width on scroll direction.
230 if (sizeDeligate.Layout == null)
232 width = sizeDeligate.WidthSpecification;
233 height = sizeDeligate.HeightSpecification;
237 MeasureChild(colView, sizeDeligate);
239 width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
240 height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
242 // pick the StepCandidate.
243 Extents itemMargin = sizeDeligate.Margin;
244 StepCandidate = IsHorizontal?
245 width + itemMargin.Start + itemMargin.End:
246 height + itemMargin.Top + itemMargin.Bottom;
247 CandidateMargin = new Extents(itemMargin);
248 if (StepCandidate == 0) StepCandidate = 1; //????
250 colView.UnrealizeItem(sizeDeligate);
253 float Current = IsHorizontal? Padding.Start : Padding.Top;
254 IGroupableItemSource source = colView.InternalItemSource;
255 GroupInfo currentGroup = null;
256 object currentParent = null;
257 for (int i = 0; i < count; i++)
259 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
261 if (i == 0 && hasHeader)
262 ItemSize.Add(headerSize);
263 else if (i == count - 1 && hasFooter)
264 ItemSize.Add(footerSize);
265 else if (source.IsGroupHeader(i))
266 ItemSize.Add(groupHeaderSize);
267 else if (source.IsGroupFooter(i))
268 ItemSize.Add(groupFooterSize);
269 else ItemSize.Add(StepCandidate);
273 if (source.IsHeader(i))
275 //ItemPosition.Add(Current);
276 Current += headerSize;
278 else if (source.IsFooter(i))
280 //ItemPosition.Add(Current);
281 Current += footerSize;
285 //GroupHeader must always exist in group usage.
286 //if (source.IsGroupHeader(i))
287 if (source.GetGroupParent(i) != currentParent)
289 currentParent = source.GetGroupParent(i);
290 float currentSize = (source.IsGroupHeader(i)? groupHeaderSize :
291 (source.IsGroupFooter(i)? groupFooterSize: StepCandidate));
292 currentGroup = new GroupInfo()
294 GroupParent = currentParent,
299 GroupSize = currentSize,
300 GroupPosition = Current
302 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
303 currentGroup.ItemPosition.Add(0);
304 groups.Add(currentGroup);
305 if (source.IsGroupHeader(i)) Current += currentSize;
308 else if (source.IsGroupFooter(i))
310 //currentGroup.hasFooter = true;
311 currentGroup.Count++;
312 currentGroup.GroupSize += groupFooterSize;
313 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
314 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
315 Current += groupFooterSize;
319 currentGroup.Count++;
320 currentGroup.GroupSize += StepCandidate;
321 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
322 currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
323 Current += StepCandidate;
329 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
330 ItemPosition.Add(Current);
332 if (i == 0 && hasHeader) Current += headerSize;
333 else if (i == count - 1 && hasFooter) Current += footerSize;
334 else Current += StepCandidate;
338 ScrollContentSize = Current + (IsHorizontal? Padding.End : Padding.Bottom);
339 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
340 else colView.ContentContainer.SizeHeight = ScrollContentSize;
342 base.Initialize(view);
343 //Console.WriteLine("[NUI] Init Done, StepCnadidate{0}, Scroll{1}", StepCandidate, ScrollContentSize);
347 /// This is called to find out where items are lain out according to current scroll position.
349 /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
350 /// <param name="force">boolean force flag to layouting forcely.</param>
351 /// <since_tizen> 9 </since_tizen>
352 public override void RequestLayout(float scrollPosition, bool force = false)
354 // Layouting is only possible after once it initialized.
355 if (!IsInitialized) return;
357 if (requestLayoutTimer != null)
359 requestLayoutTimer.Dispose();
360 requestLayoutTimer = null;
364 int LastIndex = colView.InternalItemSource.Count - 1;
366 if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
367 PrevScrollPosition = Math.Abs(scrollPosition);
369 if (ItemSizeChanged >= 0)
371 for (int i = ItemSizeChanged; i <= LastIndex; i++)
373 (float updateX, float updateY) = GetItemPosition(LastIndex);
374 ScrollContentSize = GetItemStepSize(LastIndex) + (IsHorizontal? updateX + Padding.End : updateY + Padding.Bottom);
377 int prevFirstVisible = FirstVisible;
378 int prevLastVisible = LastVisible;
380 (float X, float Y) visibleArea = (PrevScrollPosition,
381 PrevScrollPosition + (IsHorizontal? colView.Size.Width : colView.Size.Height)
384 // 1. Set First/Last Visible Item Index.
385 (int start, int end) = FindVisibleItems(visibleArea);
386 FirstVisible = start;
389 // 2. Unrealize invisible items.
390 List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
391 foreach (RecyclerViewItem item in VisibleItems)
393 if (item.Index < FirstVisible || item.Index > LastVisible)
395 //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
396 unrealizedItems.Add(item);
397 colView.UnrealizeItem(item);
400 VisibleItems.RemoveAll(unrealizedItems.Contains);
401 unrealizedItems.Clear();
403 // 3. Realize and placing visible items.
404 for (int i = FirstVisible; i <= LastVisible; i++)
406 RecyclerViewItem item = null;
407 // 4. Get item if visible or realize new.
408 if (i >= prevFirstVisible && i <= prevLastVisible)
410 item = GetVisibleItem(i);
411 if (item != null && !force) continue;
416 item = colView.RealizeItem(i);
417 if (item != null) VisibleItems.Add(item);
418 else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]");
421 (float posX, float posY) = GetItemPosition(i);
422 item.Position = new Position(posX, posY);
424 var size = (IsHorizontal? item.SizeWidth: item.SizeHeight);
426 if (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst)
428 if (item.IsHeader || item.IsFooter || item.isGroupHeader || item.isGroupFooter)
430 if (item.IsHeader) size = headerSize;
431 else if (item.IsFooter) size = footerSize;
432 else if (item.isGroupHeader) size = groupHeaderSize;
433 else if (item.isGroupFooter) size = groupFooterSize;
435 else size = StepCandidate;
437 if (IsHorizontal && item.HeightSpecification == LayoutParamPolicies.MatchParent)
439 item.Size = new Size(size, Container.Size.Height - Padding.Top - Padding.Bottom - item.Margin.Top - item.Margin.Bottom);
441 else if (!IsHorizontal && item.WidthSpecification == LayoutParamPolicies.MatchParent)
443 item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, size);
450 /// Clear the current screen and all properties.
452 [EditorBrowsable(EditorBrowsableState.Never)]
453 public override void Clear()
456 if (requestLayoutTimer != null)
458 requestLayoutTimer.Dispose();
463 foreach (GroupInfo group in groups)
465 //group.ItemPosition?.Clear();
472 if (ItemPosition != null)
474 ItemPosition.Clear();
476 if (ItemSize != null)
480 if (headerMargin != null)
482 headerMargin.Dispose();
485 if (footerMargin != null)
487 footerMargin.Dispose();
490 if (groupHeaderMargin != null)
492 groupHeaderMargin.Dispose();
493 groupHeaderMargin = null;
495 if (groupFooterMargin != null)
497 groupFooterMargin.Dispose();
498 groupFooterMargin = null;
506 [EditorBrowsable(EditorBrowsableState.Never)]
507 public override void NotifyItemSizeChanged(RecyclerViewItem item)
510 throw new ArgumentNullException(nameof(item));
512 if (!IsInitialized ||
513 (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst &&
518 float PrevSize, CurrentSize;
519 if (item.Index == (colView.InternalItemSource.Count - 1))
521 PrevSize = ScrollContentSize - ItemPosition[item.Index];
525 PrevSize = ItemPosition[item.Index + 1] - ItemPosition[item.Index];
528 CurrentSize = (IsHorizontal? item.Size.Width : item.Size.Height);
530 if (CurrentSize != PrevSize)
532 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
533 ItemSize[item.Index] = CurrentSize;
535 StepCandidate = CurrentSize;
537 if (ItemSizeChanged == -1) ItemSizeChanged = item.Index;
538 else ItemSizeChanged = Math.Min(ItemSizeChanged, item.Index);
540 //ScrollContentSize += Diff; UpdateOnce?
544 [EditorBrowsable(EditorBrowsableState.Never)]
545 public override void NotifyItemInserted(IItemSource source, int startIndex)
547 // Insert Single item.
548 if (source == null) throw new ArgumentNullException(nameof(source));
555 // Will be null if not a group.
556 float currentSize = StepCandidate;
557 IGroupableItemSource gSource = source as IGroupableItemSource;
559 // Get the first Visible Position to adjust.
561 int topInScreenIndex = 0;
563 (topInScreenIndex, offset) = FindTopItemInScreen();
566 // 1. Handle MeasureAll
568 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
574 //2. Handle Group Case.
575 if (isGrouped && gSource != null)
577 GroupInfo groupInfo = null;
578 object groupParent = gSource.GetGroupParent(startIndex);
579 int parentIndex = gSource.GetPosition(groupParent);
580 if (gSource.HasHeader) parentIndex--;
582 // Check item is group parent or not
583 // if group parent, add new gorupinfo
584 if (gSource.IsHeader(startIndex))
586 // This is childless group.
587 // create new groupInfo!
588 groupInfo = new GroupInfo()
590 GroupParent = groupParent,
591 StartIndex = startIndex,
593 GroupSize = groupHeaderSize,
596 if (parentIndex >= groups.Count)
598 groupInfo.GroupPosition = ScrollContentSize;
599 groups.Add(groupInfo);
603 groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
604 groups.Insert(parentIndex, groupInfo);
607 currentSize = groupHeaderSize;
611 // If not group parent, add item into the groupinfo.
612 if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts.");
613 groupInfo = groups[parentIndex];//GetGroupInfo(groupParent);
614 if (groupInfo == null) throw new Exception("Cannot find group information!");
617 if (gSource.IsGroupFooter(startIndex))
619 // It doesn't make sence to adding footer by notify...
620 // if GroupFooterTemplate is added,
621 // need to implement on here.
625 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
627 float curPos = groupInfo.ItemPosition[startIndex - groupInfo.StartIndex];
628 groupInfo.ItemPosition.Insert(startIndex - groupInfo.StartIndex, curPos);
629 for (int i = startIndex - groupInfo.StartIndex; i < groupInfo.Count; i++)
631 groupInfo.ItemPosition[i] = curPos;
632 curPos += GetItemStepSize(parentIndex + i);
634 groupInfo.GroupSize = curPos;
638 groupInfo.GroupSize += currentSize;
643 if (parentIndex + 1 < groups.Count)
645 for(int i = parentIndex + 1; i < groups.Count; i++)
647 groups[i].GroupPosition += currentSize;
648 groups[i].StartIndex++;
654 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
656 // Need to Implements
661 // 3. Update Scroll Content Size
662 ScrollContentSize += currentSize;
664 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
665 else colView.ContentContainer.SizeHeight = ScrollContentSize;
667 // 4. Update Visible Items.
668 foreach (RecyclerViewItem item in VisibleItems)
670 if (item.Index >= startIndex)
677 float scrollPosition = PrevScrollPosition;
681 // Insertion above Top Visible!
682 if (startIndex <= topInScreenIndex)
684 scrollPosition = GetItemPosition(topInScreenIndex);
685 scrollPosition -= offset;
687 colView.ScrollTo(scrollPosition);
691 // Update Viewport in delay.
692 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
693 // but currently we do not have any accessor to pre-calculation so instead of this,
694 // using Timer temporarily.
695 DelayedRequestLayout(scrollPosition);
699 [EditorBrowsable(EditorBrowsableState.Never)]
700 public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
703 if (source == null) throw new ArgumentNullException(nameof(source));
710 float currentSize = StepCandidate;
711 // Will be null if not a group.
712 IGroupableItemSource gSource = source as IGroupableItemSource;
714 // Get the first Visible Position to adjust.
716 int topInScreenIndex = 0;
718 (topInScreenIndex, offset) = FindTopItemInScreen();
721 // 1. Handle MeasureAll
723 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
729 // 2. Handle Group Case
730 // Adding ranged items should all same new groups.
731 if (isGrouped && gSource != null)
733 GroupInfo groupInfo = null;
734 object groupParent = gSource.GetGroupParent(startIndex);
735 int parentIndex = gSource.GetPosition(groupParent);
736 if (gSource.HasHeader) parentIndex--;
738 // We guess here that range inserted from GroupStartIndex.
739 int groupStartIndex = startIndex;
741 for (int current = startIndex; current - startIndex < count; current++)
743 // Check item is group parent or not
744 // if group parent, add new gorupinfo
745 if (groupStartIndex == current)
747 currentSize = (gSource.IsGroupHeader(current)? groupHeaderSize :
748 (gSource.IsGroupFooter(current)? groupFooterSize: currentSize));
749 //create new groupInfo!
750 groupInfo = new GroupInfo()
752 GroupParent = groupParent,
753 StartIndex = current,
755 GroupSize = currentSize,
761 //if not group parent, add item into the groupinfo.
762 //groupInfo = GetGroupInfo(groupStartIndex);
763 if (groupInfo == null) throw new Exception("Cannot find group information!");
766 if (gSource.IsGroupFooter(current))
768 groupInfo.GroupSize += groupFooterSize;
772 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
776 float curPos = groupInfo.ItemPosition[current - groupStartIndex];
777 groupInfo.ItemPosition.Insert(current - groupStartIndex, curPos);
778 for (int i = current - groupStartIndex; i < groupInfo.Count; i++)
780 groupInfo.ItemPosition[i] = curPos;
781 curPos += GetItemSize(parentIndex + i);
783 groupInfo.GroupSize = curPos;
788 groupInfo.GroupSize += StepCandidate;
794 if (parentIndex >= groups.Count)
796 groupInfo.GroupPosition = ScrollContentSize;
797 groups.Add(groupInfo);
801 groupInfo.GroupPosition = groups[parentIndex].GroupPosition;
802 groups.Insert(parentIndex, groupInfo);
805 // Update other below group's position
806 if (parentIndex + 1 < groups.Count)
808 for(int i = parentIndex + 1; i < groups.Count; i++)
810 groups[i].GroupPosition += groupInfo.GroupSize;
811 groups[i].StartIndex += count;
815 ScrollContentSize += groupInfo.GroupSize;
819 Tizen.Log.Error("NUI", "Not support insert ungrouped range items currently!");
822 // 3. Update Scroll Content Size
823 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
824 else colView.ContentContainer.SizeHeight = ScrollContentSize;
826 // 4. Update Visible Items.
827 foreach (RecyclerViewItem item in VisibleItems)
829 if (item.Index >= startIndex)
836 float scrollPosition = PrevScrollPosition;
838 // Insertion above Top Visible!
839 if (startIndex + count <= topInScreenIndex)
841 scrollPosition = GetItemPosition(topInScreenIndex);
842 scrollPosition -= offset;
844 colView.ScrollTo(scrollPosition);
848 // Update Viewport in delay.
849 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
850 // but currently we do not have any accessor to pre-calculation so instead of this,
851 // using Timer temporarily.
852 DelayedRequestLayout(scrollPosition);
856 [EditorBrowsable(EditorBrowsableState.Never)]
857 public override void NotifyItemRemoved(IItemSource source, int startIndex)
860 if (source == null) throw new ArgumentNullException(nameof(source));
862 // Will be null if not a group.
863 float currentSize = StepCandidate;
864 IGroupableItemSource gSource = source as IGroupableItemSource;
866 // Get the first Visible Position to adjust.
868 int topInScreenIndex = 0;
870 (topInScreenIndex, offset) = FindTopItemInScreen();
873 // 1. Handle MeasureAll
875 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
881 // 2. Handle Group Case
882 if (isGrouped && gSource != null)
885 GroupInfo groupInfo = null;
886 foreach(GroupInfo cur in groups)
888 if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex))
895 if (groupInfo == null) throw new Exception("Cannot find group information!");
896 // Check item is group parent or not
897 // if group parent, add new gorupinfo
898 if (groupInfo.StartIndex == startIndex)
900 // This is empty group!
901 // check group is empty.
902 if (groupInfo.Count != 1)
904 throw new Exception("Cannot remove group parent");
906 currentSize = groupInfo.GroupSize;
909 // groupInfo.Dispose();
910 groups.Remove(groupInfo);
916 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
918 //Need to Implement this.
922 groupInfo.GroupSize -= currentSize;
926 for (int i = parentIndex + 1; i < groups.Count; i++)
928 groups[i].GroupPosition -= currentSize;
929 groups[i].StartIndex--;
934 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
936 // Need to Implements
938 // else Nothing to Do
941 ScrollContentSize -= currentSize;
943 // 3. Update Scroll Content Size
944 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
945 else colView.ContentContainer.SizeHeight = ScrollContentSize;
947 // 4. Update Visible Items.
948 RecyclerViewItem targetItem = null;
949 foreach (RecyclerViewItem item in VisibleItems)
951 if (item.Index == startIndex)
954 colView.UnrealizeItem(item);
956 else if (item.Index > startIndex)
961 VisibleItems.Remove(targetItem);
964 float scrollPosition = PrevScrollPosition;
966 // Insertion above Top Visible!
967 if (startIndex <= topInScreenIndex)
969 scrollPosition = GetItemPosition(topInScreenIndex);
970 scrollPosition -= offset;
972 colView.ScrollTo(scrollPosition);
976 // Update Viewport in delay.
977 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
978 // but currently we do not have any accessor to pre-calculation so instead of this,
979 // using Timer temporarily.
980 DelayedRequestLayout(scrollPosition);
984 [EditorBrowsable(EditorBrowsableState.Never)]
985 public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
988 if (source == null) throw new ArgumentNullException(nameof(source));
990 // Will be null if not a group.
991 float currentSize = StepCandidate;
992 IGroupableItemSource gSource = source as IGroupableItemSource;
994 // Get the first Visible Position to adjust.
996 int topInScreenIndex = 0;
998 (topInScreenIndex, offset) = FindTopItemInScreen();
1001 // 1. Handle MeasureAll
1003 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1009 // 2. Handle Group Case
1010 if (isGrouped && gSource != null)
1012 int parentIndex = 0;
1013 GroupInfo groupInfo = null;
1014 foreach(GroupInfo cur in groups)
1016 if ((cur.StartIndex == startIndex) && (cur.Count == count))
1023 if (groupInfo == null) throw new Exception("Cannot find group information!");
1024 // Check item is group parent or not
1025 // if group parent, add new gorupinfo
1026 currentSize = groupInfo.GroupSize;
1027 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1029 // Update ItemSize and ItemPosition
1032 // groupInfo.Dispose();
1033 groups.Remove(groupInfo);
1035 for (int i = parentIndex; i < groups.Count; i++)
1037 groups[i].GroupPosition -= currentSize;
1038 groups[i].StartIndex -= count;
1043 //It must group case! throw exception!
1044 Tizen.Log.Error("NUI", "Not support remove ungrouped range items currently!");
1047 ScrollContentSize -= currentSize;
1049 // 3. Update Scroll Content Size
1050 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1051 else colView.ContentContainer.SizeHeight = ScrollContentSize;
1053 // 4. Update Visible Items.
1054 List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
1055 foreach (RecyclerViewItem item in VisibleItems)
1057 if ((item.Index >= startIndex)
1058 && (item.Index < startIndex + count))
1060 unrealizedItems.Add(item);
1061 colView.UnrealizeItem(item);
1063 else if (item.Index >= startIndex + count)
1065 item.Index -= count;
1068 VisibleItems.RemoveAll(unrealizedItems.Contains);
1069 unrealizedItems.Clear();
1072 float scrollPosition = PrevScrollPosition;
1074 // Insertion above Top Visible!
1075 if (startIndex <= topInScreenIndex)
1077 scrollPosition = GetItemPosition(topInScreenIndex);
1078 scrollPosition -= offset;
1080 colView.ScrollTo(scrollPosition);
1084 // Update Viewport in delay.
1085 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1086 // but currently we do not have any accessor to pre-calculation so instead of this,
1087 // using Timer temporarily.
1088 DelayedRequestLayout(scrollPosition);
1092 [EditorBrowsable(EditorBrowsableState.Never)]
1093 public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
1096 if (source == null) throw new ArgumentNullException(nameof(source));
1098 // Will be null if not a group.
1099 float currentSize = StepCandidate;
1100 int diff = toPosition - fromPosition;
1102 // Get the first Visible Position to adjust.
1104 int topInScreenIndex = 0;
1106 (topInScreenIndex, offset) = FindTopItemInScreen();
1109 // 1. Handle MeasureAll
1111 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1117 // Move can only happen in it's own groups.
1118 // so there will be no changes in position, startIndex in ohter groups.
1119 // check visible item and update indexs.
1120 int startIndex = ( diff > 0 ? fromPosition: toPosition);
1121 int endIndex = (diff > 0 ? toPosition: fromPosition);
1123 if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1125 foreach (RecyclerViewItem item in VisibleItems)
1127 if ((item.Index >= startIndex)
1128 && (item.Index <= endIndex))
1130 if (item.Index == fromPosition) item.Index = toPosition;
1133 if (diff > 0) item.Index--;
1140 if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
1141 else colView.ContentContainer.SizeHeight = ScrollContentSize;
1144 float scrollPosition = PrevScrollPosition;
1146 // Insertion above Top Visible!
1147 if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1148 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1150 scrollPosition = GetItemPosition(topInScreenIndex);
1151 scrollPosition -= offset;
1153 colView.ScrollTo(scrollPosition);
1157 // Update Viewport in delay.
1158 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1159 // but currently we do not have any accessor to pre-calculation so instead of this,
1160 // using Timer temporarily.
1161 DelayedRequestLayout(scrollPosition);
1165 [EditorBrowsable(EditorBrowsableState.Never)]
1166 public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count)
1169 if (source == null) throw new ArgumentNullException(nameof(source));
1171 // Will be null if not a group.
1172 float currentSize = StepCandidate;
1173 int diff = toPosition - fromPosition;
1175 int startIndex = ( diff > 0 ? fromPosition: toPosition);
1176 int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1);
1178 // 2. Handle Group Case
1181 int fromParentIndex = 0;
1182 int toParentIndex = 0;
1183 bool findFrom = false;
1184 bool findTo = false;
1185 GroupInfo fromGroup = null;
1186 GroupInfo toGroup = null;
1188 foreach(GroupInfo cur in groups)
1190 if ((cur.StartIndex == fromPosition) && (cur.Count == count))
1194 if (findFrom && findTo) break;
1196 else if (cur.StartIndex == toPosition)
1200 if (findFrom && findTo) break;
1202 if (!findFrom) fromParentIndex++;
1203 if (!findTo) toParentIndex++;
1205 if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!");
1207 fromGroup.StartIndex = toGroup.StartIndex;
1208 fromGroup.GroupPosition = toGroup.GroupPosition;
1210 endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1);
1212 groups.Remove(fromGroup);
1213 groups.Insert(toParentIndex, fromGroup);
1215 int startGroup = (diff > 0? fromParentIndex: toParentIndex);
1216 int endGroup = (diff > 0? toParentIndex: fromParentIndex);
1218 for (int i = startGroup; i <= endGroup; i++)
1220 if (i == toParentIndex) continue;
1221 float prevPos = groups[i].GroupPosition;
1222 int prevIdx = groups[i].StartIndex;
1223 groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize;
1224 groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count;
1229 //It must group case! throw exception!
1230 Tizen.Log.Error("NUI", "Not support move ungrouped range items currently!");
1233 // Move can only happen in it's own groups.
1234 // so there will be no changes in position, startIndex in ohter groups.
1235 // check visible item and update indexs.
1236 if ((endIndex >= FirstVisible) && (startIndex <= LastVisible))
1238 foreach (RecyclerViewItem item in VisibleItems)
1240 if ((item.Index >= startIndex)
1241 && (item.Index <= endIndex))
1243 if ((item.Index >= fromPosition) && (item.Index < fromPosition + count))
1245 item.Index = fromPosition - item.Index + toPosition;
1249 if (diff > 0) item.Index -= count;
1250 else item.Index += count;
1257 float scrollPosition = PrevScrollPosition;
1259 // Insertion above Top Visible!
1260 if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) ||
1261 ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex)))
1263 scrollPosition = GetItemPosition(topInScreenIndex);
1264 scrollPosition -= offset;
1266 colView.ScrollTo(scrollPosition);
1270 // Update Viewport in delay.
1271 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1272 // but currently we do not have any accessor to pre-calculation so instead of this,
1273 // using Timer temporarily.
1274 DelayedRequestLayout(scrollPosition);
1278 [EditorBrowsable(EditorBrowsableState.Never)]
1279 public override void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
1282 if (source == null) throw new ArgumentNullException(nameof(source));
1283 IGroupableItemSource gSource = source as IGroupableItemSource;
1284 if (gSource == null)throw new Exception("Source is not group!");
1286 // Get the first Visible Position to adjust.
1288 int topInScreenIndex = 0;
1290 (topInScreenIndex, offset) = FindTopItemInScreen();
1294 // Unrealize, initialized all items in the Range
1297 // Update Viewport in delay.
1298 // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop.
1299 // but currently we do not have any accessor to pre-calculation so instead of this,
1300 // using Timer temporarily.
1301 DelayedRequestLayout(PrevScrollPosition);
1305 [EditorBrowsable(EditorBrowsableState.Never)]
1306 public override float CalculateLayoutOrientationSize()
1308 //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
1309 return ScrollContentSize;
1313 [EditorBrowsable(EditorBrowsableState.Never)]
1314 public override float CalculateCandidateScrollPosition(float scrollPosition)
1316 //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
1317 return scrollPosition;
1321 [EditorBrowsable(EditorBrowsableState.Never)]
1322 public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1324 if (currentFocusedView == null)
1325 throw new ArgumentNullException(nameof(currentFocusedView));
1327 View nextFocusedView = null;
1328 int targetSibling = -1;
1332 case View.FocusDirection.Left:
1334 targetSibling = IsHorizontal? currentFocusedView.SiblingOrder - 1 : targetSibling;
1337 case View.FocusDirection.Right:
1339 targetSibling = IsHorizontal? currentFocusedView.SiblingOrder + 1 : targetSibling;
1342 case View.FocusDirection.Up:
1344 targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder - 1;
1347 case View.FocusDirection.Down:
1349 targetSibling = IsHorizontal? targetSibling : currentFocusedView.SiblingOrder + 1;
1354 if (targetSibling > -1 && targetSibling < Container.Children.Count)
1356 RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
1357 if (candidate != null && candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
1359 nextFocusedView = candidate;
1363 return nextFocusedView;
1367 [EditorBrowsable(EditorBrowsableState.Never)]
1368 protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
1370 int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter? 1 : 0);
1373 (int start, int end) found = (0, 0);
1375 // 1. Find the start index.
1376 // Header is Showing
1377 if (hasHeader && visibleArea.X <= headerSize + (IsHorizontal? Padding.Start: Padding.Top))
1386 foreach (GroupInfo gInfo in groups)
1390 if (gInfo.GroupPosition <= visibleArea.X &&
1391 gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
1393 for (int i = 0; i < gInfo.Count; i++)
1395 // Reach last index of group.
1396 if (i == (gInfo.Count - 1))
1398 found.start = gInfo.StartIndex + i - adds;
1403 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.X &&
1404 GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.X)
1406 found.start = gInfo.StartIndex + i - adds;
1413 //footer only shows?
1416 found.start = MaxIndex;
1421 float visibleAreaX = visibleArea.X - (hasHeader? headerSize : 0);
1422 found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / StepCandidate)) - adds);
1425 if (found.start < 0) found.start = 0;
1428 if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
1430 found.end = MaxIndex + 1;
1437 // can it be start from founded group...?
1438 //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
1439 foreach (GroupInfo gInfo in groups)
1442 if (gInfo.GroupPosition <= visibleArea.Y &&
1443 gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
1445 for (int i = 0; i < gInfo.Count; i++)
1447 if (i == (gInfo.Count - 1))
1449 //Should be groupFooter!
1450 found.end = gInfo.StartIndex + i + adds;
1455 else if (GetGroupPosition(gInfo, gInfo.StartIndex + i) <= visibleArea.Y &&
1456 GetGroupPosition(gInfo, gInfo.StartIndex + i + 1) >= visibleArea.Y)
1458 found.end = gInfo.StartIndex + i + adds;
1465 if (failed) found.end = MaxIndex;
1469 float visibleAreaY = visibleArea.Y - (hasHeader? headerSize : 0);
1470 found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / StepCandidate)) + adds);
1471 if (hasHeader) found.end += 1;
1473 if (found.end > (MaxIndex)) found.end = MaxIndex;
1478 // Item position excluding margins.
1479 internal override (float X, float Y) GetItemPosition(int index)
1481 int spaceStartX = Padding.Start;
1482 int spaceStartY = Padding.Top;
1483 if (colView.InternalItemSource.IsHeader(index))
1485 return (spaceStartX + headerMargin.Start, spaceStartY + headerMargin.Top);
1487 else if (colView.InternalItemSource.IsFooter(index))
1489 return ((IsHorizontal? ScrollContentSize - footerSize - Padding.End + footerMargin.Start : spaceStartX + footerMargin.Start),
1490 (IsHorizontal? spaceStartY + footerMargin.Top : ScrollContentSize - footerSize - Padding.Bottom + footerMargin.Top));
1494 GroupInfo gInfo = GetGroupInfo(index);
1497 Tizen.Log.Error("NUI", "GroupInfo failed to get in GetItemPosition()!");
1500 float current = GetGroupPosition(gInfo, index);
1501 Extents itemMargin = CandidateMargin;
1503 if (colView.InternalItemSource.IsGroupHeader(index))
1505 itemMargin = groupHeaderMargin;
1507 else if (colView.InternalItemSource.IsGroupFooter(index))
1509 itemMargin = groupFooterMargin;
1511 return ((IsHorizontal?
1512 itemMargin.Start + GetGroupPosition(gInfo, index):
1513 spaceStartX + itemMargin.Start),
1515 spaceStartY + itemMargin.Top:
1516 itemMargin.Top + GetGroupPosition(gInfo, index)));
1518 else if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1520 //FIXME : CandidateMargin need to be actual itemMargin
1521 return ((IsHorizontal? ItemPosition[index] + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1522 (IsHorizontal? spaceStartY + CandidateMargin.Top : ItemPosition[index] + CandidateMargin.Top));
1526 int adjustIndex = index - (hasHeader ? 1 : 0);
1527 float current = (IsHorizontal ? spaceStartX : spaceStartY) + (hasHeader? headerSize : 0) + adjustIndex * StepCandidate;
1528 //FIXME : CandidateMargin need to be actual itemMargin
1529 return ((IsHorizontal? current + CandidateMargin.Start : spaceStartX + CandidateMargin.Start),
1530 (IsHorizontal? spaceStartY + CandidateMargin.Top : current + CandidateMargin.Top));
1534 // Item size excluding margins. this size is approximated size.
1535 internal override (float Width, float Height) GetItemSize(int index)
1537 if (colView.InternalItemSource.IsHeader(index))
1539 return ((IsHorizontal? (int)headerSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1540 - headerMargin.Start - headerMargin.End,
1541 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)headerSize)
1542 - headerMargin.Top - headerMargin.Bottom);
1544 else if (colView.InternalItemSource.IsFooter(index))
1546 return ((IsHorizontal? (int)footerSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1547 - footerMargin.Start - footerMargin.End,
1548 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)footerSize)
1549 - footerMargin.Top - footerMargin.Bottom);
1551 else if (colView.InternalItemSource.IsGroupHeader(index))
1553 return ((IsHorizontal? (int)groupHeaderSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1554 - groupHeaderMargin.Start - groupHeaderMargin.End,
1555 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)groupHeaderSize)
1556 - groupHeaderMargin.Top - groupHeaderMargin.Bottom);
1558 else if (colView.InternalItemSource.IsGroupFooter(index))
1560 return ((IsHorizontal? (int)groupFooterSize : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1561 - groupFooterMargin.Start - groupFooterMargin.End,
1562 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)groupFooterSize)
1563 - groupFooterMargin.Top - groupFooterMargin.Bottom);
1567 return ((IsHorizontal? (int)StepCandidate : (int)(colView.Size.Width) - Padding.Start - Padding.End)
1568 - CandidateMargin.Start - CandidateMargin.End,
1569 (IsHorizontal? (int)colView.Size.Height - Padding.Top - Padding.Bottom: (int)StepCandidate)
1570 - CandidateMargin.Top - CandidateMargin.Bottom);
1574 private void DelayedRequestLayout(float scrollPosition , bool force = true)
1576 if (requestLayoutTimer != null)
1578 requestLayoutTimer.Dispose();
1581 requestLayoutTimer = new Timer(1);
1582 requestLayoutTimer.Interval = 1;
1583 requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) =>
1585 RequestLayout(scrollPosition, force);
1591 private (int, float) FindTopItemInScreen()
1594 float offset = 0.0F, Pos, Size;
1596 foreach(RecyclerViewItem item in VisibleItems)
1598 Pos = IsHorizontal ? item.PositionX : item.PositionY;
1599 Size = IsHorizontal ? item.SizeWidth : item.SizeHeight;
1600 if (PrevScrollPosition >= Pos && PrevScrollPosition < Pos + Size)
1603 offset = Pos - PrevScrollPosition;
1608 return (index, offset);
1612 private float GetItemStepSize(int index)
1614 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1616 return ItemSize[index];
1620 if (colView.InternalItemSource.IsHeader(index))
1622 else if (colView.InternalItemSource.IsFooter(index))
1624 else if (colView.InternalItemSource.IsGroupHeader(index))
1625 return groupHeaderSize;
1626 else if (colView.InternalItemSource.IsGroupFooter(index))
1627 return groupFooterSize;
1629 return StepCandidate;
1633 private void UpdatePosition(int index)
1635 bool IsGroup = (colView.InternalItemSource is IGroupableItemSource);
1637 if (index <= 0) return;
1638 if (index >= colView.InternalItemSource.Count)
1642 //IsGroupHeader = (colView.InternalItemSource as IGroupableItemSource).IsGroupHeader(index);
1643 //IsGroupFooter = (colView.InternalItemSource as IGroupableItemSource).IsGroupFooter(index);
1646 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1647 ItemPosition[index] = ItemPosition[index - 1] + GetItemStepSize(index - 1);
1650 private RecyclerViewItem GetVisibleItem(int index)
1652 foreach (RecyclerViewItem item in VisibleItems)
1654 if (item.Index == index) return item;
1659 private GroupInfo GetGroupInfo(int index)
1661 if (Visited != null)
1663 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1666 if (hasHeader && index == 0) return null;
1667 foreach (GroupInfo group in groups)
1669 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1679 private float GetGroupPosition(GroupInfo groupInfo, int index)
1681 if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
1682 return groupInfo.GroupPosition + groupInfo.ItemPosition[index - groupInfo.StartIndex];
1685 float pos = groupInfo.GroupPosition;
1686 if (groupInfo.StartIndex == index) return pos;
1688 pos = pos + groupHeaderSize + StepCandidate * (index - groupInfo.StartIndex - 1);
1694 private object GetGroupParent(int index)
1696 if (Visited != null)
1698 if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
1699 return Visited.GroupParent;
1701 if (hasHeader && index == 0) return null;
1702 foreach (GroupInfo group in groups)
1704 if (group.StartIndex <= index && group.StartIndex + group.Count > index)
1707 return group.GroupParent;
1716 public object GroupParent;
1717 public int StartIndex;
1719 public float GroupSize;
1720 public float GroupPosition;
1721 //Items relative position from the GroupPosition. Only use for MeasureAll.
1722 public List<float> ItemPosition = new List<float>();