From: SangHyeon Jade Lee Date: Thu, 29 Apr 2021 12:16:55 +0000 (+0900) Subject: Implements view update on NotifyCollectionChanged in collectionView. (#2957) X-Git-Tag: citest_t1~32 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=838c33d69ba25fbdb40a224d3dd213fec4c286fe;p=platform%2Fcore%2Fcsapi%2Ftizenfx.git Implements view update on NotifyCollectionChanged in collectionView. (#2957) * [NUI] Implements view layout update on NotifyCollectionChanged in collectionView. When Collection is updated, View can pick the notify event with updated info. this patch implement normal collection updates including - single item inserted - single item removed - single item moved - group insrted - group removed - group moved and apply samples regarding new implemented feature. * [NUI] Change parameter name to fix warnings * [NUI] fix ahead patch typos --- diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/CollectionView.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/CollectionView.cs index 1eddf02..2fdbc35 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/CollectionView.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/CollectionView.cs @@ -17,6 +17,7 @@ using System; using System.Linq; using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Windows.Input; using System.ComponentModel; using Tizen.NUI.BaseComponents; @@ -200,6 +201,20 @@ namespace Tizen.NUI.Components } set { + if (itemsSource != null) + { + // Clearing old data! + if (itemsSource is INotifyCollectionChanged prevNotifyCollectionChanged) + { + prevNotifyCollectionChanged.CollectionChanged -= CollectionChanged; + } + itemsLayouter.Clear(); + if (selectedItem != null) selectedItem = null; + if (selectedItems != null) + { + selectedItems.Clear(); + } + } itemsSource = value; if (value == null) @@ -208,6 +223,11 @@ namespace Tizen.NUI.Components //layouter.Clear() return; } + if (itemsSource is INotifyCollectionChanged newNotifyCollectionChanged) + { + newNotifyCollectionChanged.CollectionChanged += CollectionChanged; + } + if (InternalItemSource != null) InternalItemSource.Dispose(); InternalItemSource = ItemsSourceFactory.Create(this); @@ -269,7 +289,8 @@ namespace Tizen.NUI.Components { if (layouterStyle != null) { - itemsLayouter.Padding = new Extents(layouterStyle.Padding); + if (layouterStyle.Padding != null) + itemsLayouter.Padding = new Extents(layouterStyle.Padding); } } Init(); @@ -491,6 +512,22 @@ namespace Tizen.NUI.Components /// [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyDataSetChanged() + { + if (selectedItem != null) + { + selectedItem = null; + } + if (selectedItems != null) + { + selectedItems.Clear(); + } + + base.NotifyDataSetChanged(); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled) { View nextFocusedView = null; @@ -727,6 +764,7 @@ namespace Tizen.NUI.Components // Realize and Decorate the item. internal override RecyclerViewItem RealizeItem(int index) { + RecyclerViewItem item; if (index == 0 && Header != null) { Header.Show(); @@ -751,17 +789,17 @@ namespace Tizen.NUI.Components { groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this); - groupHeader.ParentItemsView = this; groupHeader.Template = templ; groupHeader.isGroupHeader = true; groupHeader.isGroupFooter = false; ContentContainer.Add(groupHeader); } + groupHeader.ParentItemsView = this; groupHeader.Index = index; groupHeader.ParentGroup = context; groupHeader.BindingContext = context; //group selection? - return groupHeader; + item = groupHeader; } else if (InternalItemSource.IsGroupFooter(index)) { @@ -772,48 +810,48 @@ namespace Tizen.NUI.Components { groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this); - groupFooter.ParentItemsView = this; groupFooter.Template = templ; groupFooter.isGroupHeader = false; groupFooter.isGroupFooter = true; ContentContainer.Add(groupFooter); } + groupFooter.ParentItemsView = this; groupFooter.Index = index; groupFooter.ParentGroup = context; groupFooter.BindingContext = context; //group selection? - return groupFooter; + item = groupFooter; } - } - - RecyclerViewItem item = base.RealizeItem(index); - if (item != null) - { - if (isGrouped) + else { + item = base.RealizeItem(index); item.ParentGroup = InternalItemSource.GetGroupParent(index); } + } + else + { + item = base.RealizeItem(index); + } - switch (SelectionMode) - { - case ItemSelectionMode.SingleSelection: - if (item.BindingContext != null && item.BindingContext == SelectedItem) - { - item.IsSelected = true; - } - break; + switch (SelectionMode) + { + case ItemSelectionMode.SingleSelection: + if (item.BindingContext != null && item.BindingContext == SelectedItem) + { + item.IsSelected = true; + } + break; - case ItemSelectionMode.MultipleSelections: - if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false)) - { - item.IsSelected = true; - } - break; - case ItemSelectionMode.None: - item.IsSelectable = false; - break; - } + case ItemSelectionMode.MultipleSelections: + if ((item.BindingContext != null) && (SelectedItems?.Contains(item.BindingContext) ?? false)) + { + item.IsSelected = true; + } + break; + case ItemSelectionMode.None: + item.IsSelectable = false; + break; } return item; } @@ -833,12 +871,19 @@ namespace Tizen.NUI.Components } if (item.isGroupHeader || item.isGroupFooter) { + item.Index = -1; + item.ParentItemsView = null; + item.BindingContext = null; + item.IsPressed = false; + item.IsSelected = false; + item.IsEnabled = true; + item.UpdateState(); + //item.Relayout -= OnItemRelayout; if (!recycle || !PushRecycleGroupCache(item)) Utility.Dispose(item); return; } - item.IsSelected = false; base.UnrealizeItem(item, recycle); } @@ -904,6 +949,7 @@ namespace Tizen.NUI.Components ItemsLayouter.Initialize(this); needInitalizeLayouter = false; } + base.OnScrolling(source, args); } @@ -921,6 +967,10 @@ namespace Tizen.NUI.Components if (type == DisposeTypes.Explicit) { disposed = true; + + // From now on, no need to use this properties, + // so remove reference, to push it into garbage collector. + if (InternalItemSource != null) { InternalItemSource.Dispose(); @@ -938,7 +988,16 @@ namespace Tizen.NUI.Components } groupHeaderTemplate = null; groupFooterTemplate = null; - // + + if (selectedItem != null) + { + selectedItem = null; + } + if (selectedItems != null) + { + selectedItems.Clear(); + selectedItems = null; + } } base.Dispose(type); @@ -1100,6 +1159,43 @@ namespace Tizen.NUI.Components } return viewItem; } + private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + break; + case NotifyCollectionChangedAction.Remove: + // Clear removed items. + if (args.OldItems != null) + { + if (args.OldItems.Contains(selectedItem)) + { + selectedItem = null; + } + + if (selectedItems != null) + { + foreach (object removed in args.OldItems) + { + if (selectedItems.Contains(removed)) + { + selectedItems.Remove(removed); + } + } + } + } + break; + case NotifyCollectionChangedAction.Replace: + break; + case NotifyCollectionChangedAction.Move: + break; + case NotifyCollectionChangedAction.Reset: + break; + default: + throw new ArgumentOutOfRangeException(nameof(args)); + } + } } } diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ICollectionChangedNotifier.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ICollectionChangedNotifier.cs index d00695d..93abb8b 100644 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/ICollectionChangedNotifier.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/ICollectionChangedNotifier.cs @@ -57,6 +57,16 @@ namespace Tizen.NUI.Components void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition); /// + /// Notify the range of the observable items are moved from fromPosition to ToPosition. + /// + /// + /// + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count); + + /// /// Notify the range of observable items from start to end are changed. /// /// diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableGroupedSource.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableGroupedSource.cs index a68f33c..a0247d5 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableGroupedSource.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/ItemSource/ObservableGroupedSource.cs @@ -186,6 +186,13 @@ namespace Tizen.NUI.Components notifier.NotifyItemMoved(this, localFromIndex, localToIndex); } + public void NotifyItemRangeMoved(IItemSource group, int localFromIndex, int localToIndex, int count) + { + localFromIndex = GetAbsolutePosition(group, localFromIndex); + localToIndex = GetAbsolutePosition(group, localToIndex); + notifier.NotifyItemRangeMoved(this, localFromIndex, localToIndex, count); + } + public void NotifyItemRangeChanged(IItemSource group, int localStartIndex, int localEndIndex) { localStartIndex = GetAbsolutePosition(group, localStartIndex); @@ -395,13 +402,17 @@ namespace Tizen.NUI.Components void Move(NotifyCollectionChangedEventArgs args) { + var itemCount = CountItemsInGroups(args.OldStartingIndex, args.OldItems.Count); var start = Math.Min(args.OldStartingIndex, args.NewStartingIndex); - var end = Math.Max(args.OldStartingIndex, args.NewStartingIndex) + args.NewItems.Count; + var end = Math.Max(args.OldStartingIndex, args.NewStartingIndex) + itemCount; - var itemCount = CountItemsInGroups(start, end - start); - var absolutePosition = GetAbsolutePosition(groups[start], 0); + var fromPosition = GetAbsolutePosition(groups[args.OldStartingIndex], 0); + var toPosition = GetAbsolutePosition(groups[args.NewStartingIndex], 0); - notifier.NotifyItemRangeChanged(this, absolutePosition, itemCount); + // RangeChanged give unspecified information about moves. + // use RangeMoved instead of rangeChanged. + //notifier.NotifyItemRangeChanged(this, absolutePosition, itemCount); + notifier.NotifyItemRangeMoved(this, fromPosition, toPosition, itemCount); UpdateGroupTracking(); } diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs index 1f7e4e4..f3aba65 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs @@ -43,6 +43,7 @@ namespace Tizen.NUI.Components private float groupFooterSize; private Extents groupFooterMargin; private GroupInfo Visited; + private Timer requestLayoutTimer = null; /// /// Clean up ItemsLayouter. @@ -383,10 +384,14 @@ namespace Tizen.NUI.Components if (i >= prevFirstVisible && i <= prevLastVisible) { item = GetVisibleItem(i); - if (item) continue; + if (item != null && !force) continue; + } + if (item == null) + { + item = colView.RealizeItem(i); + if (item != null) VisibleItems.Add(item); + else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]"); } - if (item == null) item = colView.RealizeItem(i); - VisibleItems.Add(item); //item Position without Padding and Margin. (float x, float y) = GetItemPosition(i); @@ -410,6 +415,53 @@ namespace Tizen.NUI.Components //Console.WriteLine("Realize Done"); } + /// + /// Clear the current screen and all properties. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void Clear() + { + // Clean Up + if (requestLayoutTimer != null) + { + requestLayoutTimer.Dispose(); + } + if (groups != null) + { + /* + foreach (GroupInfo group in groups) + { + //group.ItemPosition?.Clear(); + // if Disposable? + //group.Dispose(); + } + */ + groups.Clear(); + } + if (headerMargin != null) + { + headerMargin.Dispose(); + headerMargin = null; + } + if (footerMargin != null) + { + footerMargin.Dispose(); + footerMargin = null; + } + if (groupHeaderMargin != null) + { + groupHeaderMargin.Dispose(); + groupHeaderMargin = null; + } + if (groupFooterMargin != null) + { + groupFooterMargin.Dispose(); + groupFooterMargin = null; + } + + base.Clear(); + } + /// public override void NotifyItemSizeChanged(RecyclerViewItem item) { @@ -420,6 +472,710 @@ namespace Tizen.NUI.Components /// [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemInserted(IItemSource source, int startIndex) + { + // Insert Single item. + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = 0; + IGroupableItemSource gSource = source as IGroupableItemSource; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + //2. Handle Group Case. + if (isGrouped && gSource != null) + { + GroupInfo groupInfo = null; + object groupParent = gSource.GetGroupParent(startIndex); + int parentIndex = gSource.GetPosition(groupParent); + if (gSource.HasHeader) parentIndex--; + + // Check item is group parent or not + // if group parent, add new gorupinfo + if (gSource.IsHeader(startIndex)) + { + // This is childless group. + // create new groupInfo! + groupInfo = new GroupInfo() + { + GroupParent = groupParent, + StartIndex = startIndex, + Count = 1, + GroupSize = groupHeaderSize, + }; + + if (parentIndex >= groups.Count) + { + groupInfo.GroupPosition = ScrollContentSize; + groups.Add(groupInfo); + } + else + { + groupInfo.GroupPosition = groups[parentIndex].GroupPosition; + groups.Insert(parentIndex, groupInfo); + } + + currentSize = groupHeaderSize; + } + else + { + // If not group parent, add item into the groupinfo. + if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts."); + groupInfo = groups[parentIndex];//GetGroupInfo(groupParent); + if (groupInfo == null) throw new Exception("Cannot find group information!"); + + if (gSource.IsGroupFooter(startIndex)) + { + // It doesn't make sence to adding footer by notify... + // if GroupFooterTemplate is added, + // need to implement on here. + } + else + { + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Wrong! Grid Layouter do not support MeasureAll! + } + else + { + int pureCount = groupInfo.Count - 1 - (colView.GroupFooterTemplate == null? 0: 1); + if (pureCount % spanSize == 0) + { + currentSize = StepCandidate; + groupInfo.GroupSize += currentSize; + } + + } + } + groupInfo.Count++; + + } + + if (parentIndex + 1 < groups.Count) + { + for(int i = parentIndex + 1; i < groups.Count; i++) + { + groups[i].GroupPosition += currentSize; + groups[i].StartIndex++; + } + } + } + else + { + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Wrong! Grid Layouter do not support MeasureAll! + } + int pureCount = colView.InternalItemSource.Count - (hasHeader? 1: 0) - (hasFooter? 1: 0); + + // Count comes after updated in ungrouped case! + if (pureCount % spanSize == 1) + { + currentSize = StepCandidate; + } + } + + // 3. Update Scroll Content Size + ScrollContentSize += currentSize; + + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // 4. Update Visible Items. + foreach (RecyclerViewItem item in VisibleItems) + { + if (item.Index >= startIndex) + { + item.Index++; + } + } + + float scrollPosition = PrevScrollPosition; + + /* + // Position Adjust + // Insertion above Top Visible! + if (startIndex <= topInScreenIndex) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count) + { + // Insert Group + if (source == null) throw new ArgumentNullException(nameof(source)); + + float currentSize = 0; + // Will be null if not a group. + IGroupableItemSource gSource = source as IGroupableItemSource; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // 2. Handle Group Case + // Adding ranged items should all same new groups. + if (isGrouped && gSource != null) + { + GroupInfo groupInfo = null; + object groupParent = gSource.GetGroupParent(startIndex); + int parentIndex = gSource.GetPosition(groupParent); + if (gSource.HasHeader) parentIndex--; + int groupStartIndex = 0; + if (gSource.IsGroupHeader(startIndex)) + { + groupStartIndex = startIndex; + } + else + { + //exception case! + throw new Exception("Inserted wrong groups!"); + } + + for (int current = startIndex; current - startIndex < count; current++) + { + // Check item is group parent or not + // if group parent, add new gorupinfo + if (groupStartIndex == current) + { + //create new groupInfo! + groupInfo = new GroupInfo() + { + GroupParent = groupParent, + StartIndex = current, + Count = 1, + GroupSize = groupHeaderSize, + }; + currentSize += groupHeaderSize; + + } + else + { + //if not group parent, add item into the groupinfo. + //groupInfo = GetGroupInfo(groupStartIndex); + if (groupInfo == null) throw new Exception("Cannot find group information!"); + groupInfo.Count++; + + if (gSource.IsGroupFooter(current)) + { + groupInfo.GroupSize += groupFooterSize; + currentSize += groupFooterSize; + } + else + { + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Wrong! Grid Layouter do not support MeasureAll! + } + else + { + int index = current - groupInfo.StartIndex - 1; // groupHeader must always exist. + if ((index % spanSize) == 0) + { + groupInfo.GroupSize += StepCandidate; + currentSize += StepCandidate; + } + } + } + } + } + + if (parentIndex >= groups.Count) + { + groupInfo.GroupPosition = ScrollContentSize; + groups.Add(groupInfo); + } + else + { + groupInfo.GroupPosition = groups[parentIndex].GroupPosition; + groups.Insert(parentIndex, groupInfo); + } + + // Update other below group's position + if (parentIndex + 1 < groups.Count) + { + for(int i = parentIndex + 1; i < groups.Count; i++) + { + groups[i].GroupPosition += currentSize; + groups[i].StartIndex += count; + } + } + + ScrollContentSize += currentSize; + } + else + { + throw new Exception("Cannot insert ungrouped range items!"); + } + + // 3. Update Scroll Content Size + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // 4. Update Visible Items. + foreach (RecyclerViewItem item in VisibleItems) + { + if (item.Index >= startIndex) + { + item.Index += count; + } + } + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (startIndex + count <= topInScreenIndex) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRemoved(IItemSource source, int startIndex) + { + // Remove Single + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = 0; + IGroupableItemSource gSource = source as IGroupableItemSource; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // 2. Handle Group Case + if (isGrouped && gSource != null) + { + int parentIndex = 0; + GroupInfo groupInfo = null; + foreach(GroupInfo cur in groups) + { + if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex)) + { + groupInfo = cur; + break; + } + parentIndex++; + } + if (groupInfo == null) throw new Exception("Cannot find group information!"); + // Check item is group parent or not + // if group parent, add new gorupinfo + if (groupInfo.StartIndex == startIndex) + { + // This is empty group! + // check group is empty. + if (groupInfo.Count != 1) + { + throw new Exception("Cannot remove group parent"); + } + currentSize = groupInfo.GroupSize; + + // Remove Group + // groupInfo.Dispose(); + groups.Remove(groupInfo); + } + else + { + groupInfo.Count--; + + // Skip footer case as footer cannot exist alone without header. + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Wrong! Grid Layouter do not support MeasureAll! + } + else + { + int pureCount = groupInfo.Count - 1 - (colView.GroupFooterTemplate == null? 0: 1); + if (pureCount % spanSize == 0) + { + currentSize = StepCandidate; + groupInfo.GroupSize -= currentSize; + } + } + } + + for (int i = parentIndex + 1; i < groups.Count; i++) + { + groups[i].GroupPosition -= currentSize; + groups[i].StartIndex--; + } + } + else + { + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Wrong! Grid Layouter do not support MeasureAll! + } + int pureCount = colView.InternalItemSource.Count - (hasHeader? 1: 0) - (hasFooter? 1: 0); + + // Count comes after updated in ungrouped case! + if (pureCount % spanSize == 0) + { + currentSize = StepCandidate; + } + + } + + ScrollContentSize -= currentSize; + + // 3. Update Scroll Content Size + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // 4. Update Visible Items. + RecyclerViewItem targetItem = null; + foreach (RecyclerViewItem item in VisibleItems) + { + if (item.Index == startIndex) + { + targetItem = item; + colView.UnrealizeItem(item); + } + else if (item.Index > startIndex) + { + item.Index--; + } + } + VisibleItems.Remove(targetItem); + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (startIndex <= topInScreenIndex) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count) + { + // Remove Group + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = StepCandidate; + IGroupableItemSource gSource = source as IGroupableItemSource; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // 1. Handle Group Case + if (isGrouped && gSource != null) + { + int parentIndex = 0; + GroupInfo groupInfo = null; + foreach(GroupInfo cur in groups) + { + if ((cur.StartIndex == startIndex) && (cur.Count == count)) + { + groupInfo = cur; + break; + } + parentIndex++; + } + if (groupInfo == null) throw new Exception("Cannot find group information!"); + // Check item is group parent or not + // if group parent, add new gorupinfo + currentSize = groupInfo.GroupSize; + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Wrong! Grid Layouter do not support MeasureAll! + } + // Remove Group + // groupInfo.Dispose(); + groups.Remove(groupInfo); + + for (int i = parentIndex; i < groups.Count; i++) + { + groups[i].GroupPosition -= currentSize; + groups[i].StartIndex -= count; + } + } + else + { + // It must group case! throw exception! + throw new Exception("Range remove must group remove!"); + } + + ScrollContentSize -= currentSize; + + // 2. Update Scroll Content Size + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // 3. Update Visible Items. + List unrealizedItems = new List(); + foreach (RecyclerViewItem item in VisibleItems) + { + if ((item.Index >= startIndex) + && (item.Index < startIndex + count)) + { + unrealizedItems.Add(item); + colView.UnrealizeItem(item); + } + else if (item.Index >= startIndex + count) + { + item.Index -= count; + } + } + VisibleItems.RemoveAll(unrealizedItems.Contains); + unrealizedItems.Clear(); + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (startIndex <= topInScreenIndex) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition) + { + // Reorder Single + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = StepCandidate; + int diff = toPosition - fromPosition; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // Move can only happen in it's own groups. + // so there will be no changes in position, startIndex in ohter groups. + // check visible item and update indexs. + int startIndex = ( diff > 0 ? fromPosition: toPosition); + int endIndex = (diff > 0 ? toPosition: fromPosition); + + if ((endIndex >= FirstVisible) && (startIndex <= LastVisible)) + { + foreach (RecyclerViewItem item in VisibleItems) + { + if ((item.Index >= startIndex) + && (item.Index <= endIndex)) + { + if (item.Index == fromPosition) item.Index = toPosition; + else + { + if (diff > 0) item.Index--; + else item.Index++; + } + } + } + } + + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) || + ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex))) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count) + { + // Reorder Groups + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = StepCandidate; + int diff = toPosition - fromPosition; + + int startIndex = ( diff > 0 ? fromPosition: toPosition); + int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1); + + // 2. Handle Group Case + if (isGrouped) + { + int fromParentIndex = 0; + int toParentIndex = 0; + bool findFrom = false; + bool findTo = false; + GroupInfo fromGroup = null; + GroupInfo toGroup = null; + + foreach(GroupInfo cur in groups) + { + if ((cur.StartIndex == fromPosition) && (cur.Count == count)) + { + fromGroup = cur; + findFrom = true; + if (findFrom && findTo) break; + } + else if (cur.StartIndex == toPosition) + { + toGroup = cur; + findTo = true; + if (findFrom && findTo) break; + } + if (!findFrom) fromParentIndex++; + if (!findTo) toParentIndex++; + } + if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!"); + + fromGroup.StartIndex = toGroup.StartIndex; + fromGroup.GroupPosition = toGroup.GroupPosition; + + endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1); + + groups.Remove(fromGroup); + groups.Insert(toParentIndex, fromGroup); + + int startGroup = (diff > 0? fromParentIndex: toParentIndex); + int endGroup = (diff > 0? toParentIndex: fromParentIndex); + + for (int i = startGroup; i <= endGroup; i++) + { + if (i == toParentIndex) continue; + float prevPos = groups[i].GroupPosition; + int prevIdx = groups[i].StartIndex; + groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize; + groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count; + } + } + else + { + //It must group case! throw exception! + throw new Exception("Range remove must group remove!"); + } + + // Move can only happen in it's own groups. + // so there will be no changes in position, startIndex in ohter groups. + // check visible item and update indexs. + if ((endIndex >= FirstVisible) && (startIndex <= LastVisible)) + { + foreach (RecyclerViewItem item in VisibleItems) + { + if ((item.Index >= startIndex) + && (item.Index <= endIndex)) + { + if ((item.Index >= fromPosition) && (item.Index < fromPosition + count)) + { + item.Index = fromPosition - item.Index + toPosition; + } + else + { + if (diff > 0) item.Index -= count; + else item.Index += count; + } + } + } + } + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) || + ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex))) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] public override float CalculateLayoutOrientationSize() { //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize); @@ -700,6 +1456,21 @@ namespace Tizen.NUI.Components return (sizeCandidate.Width - CandidateMargin.Start - CandidateMargin.End, sizeCandidate.Height - CandidateMargin.Top - CandidateMargin.Bottom); } + private void DelayedRequestLayout(float scrollPosition , bool force = true) + { + if (requestLayoutTimer != null) + { + requestLayoutTimer.Dispose(); + } + + requestLayoutTimer = new Timer(1); + requestLayoutTimer.Interval = 1; + requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) => + { + RequestLayout(scrollPosition, force); + return false; + }); + } private RecyclerViewItem GetVisibleItem(int index) { diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/ItemsLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/ItemsLayouter.cs index afc5abb..a6ddf79 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/ItemsLayouter.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/ItemsLayouter.cs @@ -153,6 +153,11 @@ namespace Tizen.NUI.Components if (ItemsView != null) ItemsView.UnrealizeItem(item, false); } VisibleItems.Clear(); + if (CandidateMargin != null) + { + CandidateMargin.Dispose(); + CandidateMargin = null; + } ItemsView = null; Container = null; } @@ -226,6 +231,18 @@ namespace Tizen.NUI.Components } /// + /// Notify the range of the observable items are moved from fromPosition to ToPosition. + /// + /// + /// + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count) + { + } + + /// /// Notify the range of observable items from start to end are changed. /// /// Dataset source. diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs index f639753..167fbc3 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs @@ -43,6 +43,7 @@ namespace Tizen.NUI.Components private float groupFooterSize; private Extents groupFooterMargin; private GroupInfo Visited; + private Timer requestLayoutTimer = null; /// /// Clean up ItemsLayouter. @@ -57,14 +58,7 @@ namespace Tizen.NUI.Components throw new ArgumentException("LinearLayouter only can be applied CollectionView.", nameof(view)); } // 1. Clean Up - foreach (RecyclerViewItem item in VisibleItems) - { - colView.UnrealizeItem(item, false); - } - VisibleItems.Clear(); - ItemPosition.Clear(); - ItemSize.Clear(); - groups.Clear(); + Clear(); FirstVisible = 0; LastVisible = 0; @@ -347,6 +341,14 @@ namespace Tizen.NUI.Components { // Layouting is only possible after once it initialized. if (!IsInitialized) return; + + if (requestLayoutTimer != null) + { + requestLayoutTimer.Dispose(); + requestLayoutTimer = null; + force = true; + } + int LastIndex = colView.InternalItemSource.Count - 1; if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return; @@ -384,6 +386,7 @@ namespace Tizen.NUI.Components } } VisibleItems.RemoveAll(unrealizedItems.Contains); + unrealizedItems.Clear(); // 3. Realize and placing visible items. for (int i = FirstVisible; i <= LastVisible; i++) @@ -393,11 +396,15 @@ namespace Tizen.NUI.Components if (i >= prevFirstVisible && i <= prevLastVisible) { item = GetVisibleItem(i); - if (item) continue; + if (item != null && !force) continue; } - if (item == null) item = colView.RealizeItem(i); - VisibleItems.Add(item); + if (item == null) + { + item = colView.RealizeItem(i); + if (item != null) VisibleItems.Add(item); + else throw new Exception("Failed to create RecycerViewItem index of ["+ i + "]"); + } // 5. Placing item. (float posX, float posY) = GetItemPosition(i); item.Position = new Position(posX, posY); @@ -411,8 +418,65 @@ namespace Tizen.NUI.Components item.Size = new Size(Container.Size.Width - Padding.Start - Padding.End - item.Margin.Start - item.Margin.End, item.Size.Height); } } + return; + } + + /// + /// Clear the current screen and all properties. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void Clear() + { + // Clean Up + if (requestLayoutTimer != null) + { + requestLayoutTimer.Dispose(); + } + if (groups != null) + { + /* + foreach (GroupInfo group in groups) + { + //group.ItemPosition?.Clear(); + // if Disposable? + //group.Dispose(); + } + */ + groups.Clear(); + } + if (ItemPosition != null) + { + ItemPosition.Clear(); + } + if (ItemSize != null) + { + ItemSize.Clear(); + } + if (headerMargin != null) + { + headerMargin.Dispose(); + headerMargin = null; + } + if (footerMargin != null) + { + footerMargin.Dispose(); + footerMargin = null; + } + if (groupHeaderMargin != null) + { + groupHeaderMargin.Dispose(); + groupHeaderMargin = null; + } + if (groupFooterMargin != null) + { + groupFooterMargin.Dispose(); + groupFooterMargin = null; + } + + base.Clear(); } + /// [EditorBrowsable(EditorBrowsableState.Never)] public override void NotifyItemSizeChanged(RecyclerViewItem item) @@ -453,6 +517,762 @@ namespace Tizen.NUI.Components /// [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemInserted(IItemSource source, int startIndex) + { + // Insert Single item. + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = StepCandidate; + IGroupableItemSource gSource = source as IGroupableItemSource; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // 1. Handle MeasureAll + /* + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + //Need To Implement + } + */ + + //2. Handle Group Case. + if (isGrouped && gSource != null) + { + GroupInfo groupInfo = null; + object groupParent = gSource.GetGroupParent(startIndex); + int parentIndex = gSource.GetPosition(groupParent); + if (gSource.HasHeader) parentIndex--; + + // Check item is group parent or not + // if group parent, add new gorupinfo + if (gSource.IsHeader(startIndex)) + { + // This is childless group. + // create new groupInfo! + groupInfo = new GroupInfo() + { + GroupParent = groupParent, + StartIndex = startIndex, + Count = 1, + GroupSize = groupHeaderSize, + }; + + if (parentIndex >= groups.Count) + { + groupInfo.GroupPosition = ScrollContentSize; + groups.Add(groupInfo); + } + else + { + groupInfo.GroupPosition = groups[parentIndex].GroupPosition; + groups.Insert(parentIndex, groupInfo); + } + + currentSize = groupHeaderSize; + } + else + { + // If not group parent, add item into the groupinfo. + if (parentIndex >= groups.Count) throw new Exception("group parent is bigger than group counts."); + groupInfo = groups[parentIndex];//GetGroupInfo(groupParent); + if (groupInfo == null) throw new Exception("Cannot find group information!"); + groupInfo.Count++; + + if (gSource.IsGroupFooter(startIndex)) + { + // It doesn't make sence to adding footer by notify... + // if GroupFooterTemplate is added, + // need to implement on here. + } + else + { + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + float curPos = groupInfo.ItemPosition[startIndex - groupInfo.StartIndex]; + groupInfo.ItemPosition.Insert(startIndex - groupInfo.StartIndex, curPos); + for (int i = startIndex - groupInfo.StartIndex; i < groupInfo.Count; i++) + { + groupInfo.ItemPosition[i] = curPos; + curPos += GetItemStepSize(parentIndex + i); + } + groupInfo.GroupSize = curPos; + } + else + { + groupInfo.GroupSize += currentSize; + } + } + } + + if (parentIndex + 1 < groups.Count) + { + for(int i = parentIndex + 1; i < groups.Count; i++) + { + groups[i].GroupPosition += currentSize; + groups[i].StartIndex++; + } + } + } + else + { + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Need to Implements + } + + } + + // 3. Update Scroll Content Size + ScrollContentSize += currentSize; + + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // 4. Update Visible Items. + foreach (RecyclerViewItem item in VisibleItems) + { + if (item.Index >= startIndex) + { + item.Index++; + } + } + + + float scrollPosition = PrevScrollPosition; + + /* + // Position Adjust + // Insertion above Top Visible! + if (startIndex <= topInScreenIndex) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRangeInserted(IItemSource source, int startIndex, int count) + { + // Insert Group + if (source == null) throw new ArgumentNullException(nameof(source)); + + float currentSize = StepCandidate; + // Will be null if not a group. + IGroupableItemSource gSource = source as IGroupableItemSource; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // 1. Handle MeasureAll + /* + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + //Need To Implement + } + */ + + // 2. Handle Group Case + // Adding ranged items should all same new groups. + if (isGrouped && gSource != null) + { + GroupInfo groupInfo = null; + object groupParent = gSource.GetGroupParent(startIndex); + int parentIndex = gSource.GetPosition(groupParent); + if (gSource.HasHeader) parentIndex--; + int groupStartIndex = 0; + if (gSource.IsGroupHeader(startIndex)) + { + groupStartIndex = startIndex; + } + else + { + //exception case! + throw new Exception("Inserted wrong groups!"); + } + + for (int current = startIndex; current - startIndex < count; current++) + { + // Check item is group parent or not + // if group parent, add new gorupinfo + if (groupStartIndex == current) + { + //create new groupInfo! + groupInfo = new GroupInfo() + { + GroupParent = groupParent, + StartIndex = current, + Count = 1, + GroupSize = groupHeaderSize, + }; + + } + else + { + //if not group parent, add item into the groupinfo. + //groupInfo = GetGroupInfo(groupStartIndex); + if (groupInfo == null) throw new Exception("Cannot find group information!"); + groupInfo.Count++; + + if (gSource.IsGroupFooter(current)) + { + groupInfo.GroupSize += groupFooterSize; + } + else + { + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + //Need To Implement + /* + float curPos = groupInfo.ItemPosition[current - groupStartIndex]; + groupInfo.ItemPosition.Insert(current - groupStartIndex, curPos); + for (int i = current - groupStartIndex; i < groupInfo.Count; i++) + { + groupInfo.ItemPosition[i] = curPos; + curPos += GetItemSize(parentIndex + i); + } + groupInfo.GroupSize = curPos; + */ + } + else + { + groupInfo.GroupSize += StepCandidate; + } + } + } + } + + if (parentIndex >= groups.Count) + { + groupInfo.GroupPosition = ScrollContentSize; + groups.Add(groupInfo); + } + else + { + groupInfo.GroupPosition = groups[parentIndex].GroupPosition; + groups.Insert(parentIndex, groupInfo); + } + + // Update other below group's position + if (parentIndex + 1 < groups.Count) + { + for(int i = parentIndex + 1; i < groups.Count; i++) + { + groups[i].GroupPosition += groupInfo.GroupSize; + groups[i].StartIndex += count; + } + } + + ScrollContentSize += groupInfo.GroupSize; + } + else + { + Tizen.Log.Error("NUI", "Not support insert ungrouped range items currently!"); + } + + // 3. Update Scroll Content Size + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // 4. Update Visible Items. + foreach (RecyclerViewItem item in VisibleItems) + { + if (item.Index >= startIndex) + { + item.Index += count; + } + } + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (startIndex + count <= topInScreenIndex) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRemoved(IItemSource source, int startIndex) + { + // Remove Single + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = StepCandidate; + IGroupableItemSource gSource = source as IGroupableItemSource; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // 1. Handle MeasureAll + /* + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + //Need To Implement + } + */ + + // 2. Handle Group Case + if (isGrouped && gSource != null) + { + int parentIndex = 0; + GroupInfo groupInfo = null; + foreach(GroupInfo cur in groups) + { + if ((cur.StartIndex <= startIndex) && (cur.StartIndex + cur.Count - 1 >= startIndex)) + { + groupInfo = cur; + break; + } + parentIndex++; + } + if (groupInfo == null) throw new Exception("Cannot find group information!"); + // Check item is group parent or not + // if group parent, add new gorupinfo + if (groupInfo.StartIndex == startIndex) + { + // This is empty group! + // check group is empty. + if (groupInfo.Count != 1) + { + throw new Exception("Cannot remove group parent"); + } + currentSize = groupInfo.GroupSize; + + // Remove Group + // groupInfo.Dispose(); + groups.Remove(groupInfo); + } + else + { + groupInfo.Count--; + + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + //Need to Implement this. + } + else + { + groupInfo.GroupSize -= currentSize; + } + } + + for (int i = parentIndex + 1; i < groups.Count; i++) + { + groups[i].GroupPosition -= currentSize; + groups[i].StartIndex--; + } + } + else + { + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Need to Implements + } + // else Nothing to Do + } + + ScrollContentSize -= currentSize; + + // 3. Update Scroll Content Size + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // 4. Update Visible Items. + RecyclerViewItem targetItem = null; + foreach (RecyclerViewItem item in VisibleItems) + { + if (item.Index == startIndex) + { + targetItem = item; + colView.UnrealizeItem(item); + } + else if (item.Index > startIndex) + { + item.Index--; + } + } + VisibleItems.Remove(targetItem); + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (startIndex <= topInScreenIndex) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count) + { + // Remove Group + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = StepCandidate; + IGroupableItemSource gSource = source as IGroupableItemSource; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // 1. Handle MeasureAll + /* + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + //Need To Implement + } + */ + + // 2. Handle Group Case + if (isGrouped && gSource != null) + { + int parentIndex = 0; + GroupInfo groupInfo = null; + foreach(GroupInfo cur in groups) + { + if ((cur.StartIndex == startIndex) && (cur.Count == count)) + { + groupInfo = cur; + break; + } + parentIndex++; + } + if (groupInfo == null) throw new Exception("Cannot find group information!"); + // Check item is group parent or not + // if group parent, add new gorupinfo + currentSize = groupInfo.GroupSize; + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + // Update ItemSize and ItemPosition + } + // Remove Group + // groupInfo.Dispose(); + groups.Remove(groupInfo); + + for (int i = parentIndex; i < groups.Count; i++) + { + groups[i].GroupPosition -= currentSize; + groups[i].StartIndex -= count; + } + } + else + { + //It must group case! throw exception! + Tizen.Log.Error("NUI", "Not support remove ungrouped range items currently!"); + } + + ScrollContentSize -= currentSize; + + // 3. Update Scroll Content Size + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // 4. Update Visible Items. + List unrealizedItems = new List(); + foreach (RecyclerViewItem item in VisibleItems) + { + if ((item.Index >= startIndex) + && (item.Index < startIndex + count)) + { + unrealizedItems.Add(item); + colView.UnrealizeItem(item); + } + else if (item.Index >= startIndex + count) + { + item.Index -= count; + } + } + VisibleItems.RemoveAll(unrealizedItems.Contains); + unrealizedItems.Clear(); + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (startIndex <= topInScreenIndex) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition) + { + // Reorder Single + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = StepCandidate; + int diff = toPosition - fromPosition; + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + // 1. Handle MeasureAll + /* + if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) + { + //Need To Implement + } + */ + + // Move can only happen in it's own groups. + // so there will be no changes in position, startIndex in ohter groups. + // check visible item and update indexs. + int startIndex = ( diff > 0 ? fromPosition: toPosition); + int endIndex = (diff > 0 ? toPosition: fromPosition); + + if ((endIndex >= FirstVisible) && (startIndex <= LastVisible)) + { + foreach (RecyclerViewItem item in VisibleItems) + { + if ((item.Index >= startIndex) + && (item.Index <= endIndex)) + { + if (item.Index == fromPosition) item.Index = toPosition; + else + { + if (diff > 0) item.Index--; + else item.Index++; + } + } + } + } + + if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize; + else colView.ContentContainer.SizeHeight = ScrollContentSize; + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) || + ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex))) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count) + { + // Reorder Groups + if (source == null) throw new ArgumentNullException(nameof(source)); + + // Will be null if not a group. + float currentSize = StepCandidate; + int diff = toPosition - fromPosition; + + int startIndex = ( diff > 0 ? fromPosition: toPosition); + int endIndex = (diff > 0 ? toPosition + count - 1: fromPosition + count - 1); + + // 2. Handle Group Case + if (isGrouped) + { + int fromParentIndex = 0; + int toParentIndex = 0; + bool findFrom = false; + bool findTo = false; + GroupInfo fromGroup = null; + GroupInfo toGroup = null; + + foreach(GroupInfo cur in groups) + { + if ((cur.StartIndex == fromPosition) && (cur.Count == count)) + { + fromGroup = cur; + findFrom = true; + if (findFrom && findTo) break; + } + else if (cur.StartIndex == toPosition) + { + toGroup = cur; + findTo = true; + if (findFrom && findTo) break; + } + if (!findFrom) fromParentIndex++; + if (!findTo) toParentIndex++; + } + if (toGroup == null || fromGroup == null) throw new Exception("Cannot find group information!"); + + fromGroup.StartIndex = toGroup.StartIndex; + fromGroup.GroupPosition = toGroup.GroupPosition; + + endIndex = (diff > 0 ? toPosition + toGroup.Count - 1: fromPosition + count - 1); + + groups.Remove(fromGroup); + groups.Insert(toParentIndex, fromGroup); + + int startGroup = (diff > 0? fromParentIndex: toParentIndex); + int endGroup = (diff > 0? toParentIndex: fromParentIndex); + + for (int i = startGroup; i <= endGroup; i++) + { + if (i == toParentIndex) continue; + float prevPos = groups[i].GroupPosition; + int prevIdx = groups[i].StartIndex; + groups[i].GroupPosition = groups[i].GroupPosition + (diff > 0? -1 : 1) * fromGroup.GroupSize; + groups[i].StartIndex = groups[i].StartIndex + (diff > 0? -1 : 1) * fromGroup.Count; + } + } + else + { + //It must group case! throw exception! + Tizen.Log.Error("NUI", "Not support move ungrouped range items currently!"); + } + + // Move can only happen in it's own groups. + // so there will be no changes in position, startIndex in ohter groups. + // check visible item and update indexs. + if ((endIndex >= FirstVisible) && (startIndex <= LastVisible)) + { + foreach (RecyclerViewItem item in VisibleItems) + { + if ((item.Index >= startIndex) + && (item.Index <= endIndex)) + { + if ((item.Index >= fromPosition) && (item.Index < fromPosition + count)) + { + item.Index = fromPosition - item.Index + toPosition; + } + else + { + if (diff > 0) item.Index -= count; + else item.Index += count; + } + } + } + } + + // Position Adjust + float scrollPosition = PrevScrollPosition; + /* + // Insertion above Top Visible! + if (((fromPosition > topInScreenIndex) && (toPosition < topInScreenIndex) || + ((fromPosition < topInScreenIndex) && (toPosition > topInScreenIndex))) + { + scrollPosition = GetItemPosition(topInScreenIndex); + scrollPosition -= offset; + + colView.ScrollTo(scrollPosition); + } + */ + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(scrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange) + { + // Reorder Group + if (source == null) throw new ArgumentNullException(nameof(source)); + IGroupableItemSource gSource = source as IGroupableItemSource; + if (gSource == null)throw new Exception("Source is not group!"); + + // Get the first Visible Position to adjust. + /* + int topInScreenIndex = 0; + float offset = 0F; + (topInScreenIndex, offset) = FindTopItemInScreen(); + */ + + + // Unrealize, initialized all items in the Range + // and receate all. + + // Update Viewport in delay. + // FIMXE: original we only need to process RequestLayout once before layout calculation in main loop. + // but currently we do not have any accessor to pre-calculation so instead of this, + // using Timer temporarily. + DelayedRequestLayout(PrevScrollPosition); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] public override float CalculateLayoutOrientationSize() { //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize); @@ -721,6 +1541,44 @@ namespace Tizen.NUI.Components } } + private void DelayedRequestLayout(float scrollPosition , bool force = true) + { + if (requestLayoutTimer != null) + { + requestLayoutTimer.Dispose(); + } + + requestLayoutTimer = new Timer(1); + requestLayoutTimer.Interval = 1; + requestLayoutTimer.Tick += ((object target, Timer.TickEventArgs args) => + { + RequestLayout(scrollPosition, force); + return false; + }); + } + + /* + private (int, float) FindTopItemInScreen() + { + int index = -1; + float offset = 0.0F, Pos, Size; + + foreach(RecyclerViewItem item in VisibleItems) + { + Pos = IsHorizontal ? item.PositionX : item.PositionY; + Size = IsHorizontal ? item.SizeWidth : item.SizeHeight; + if (PrevScrollPosition >= Pos && PrevScrollPosition < Pos + Size) + { + index = item.Index; + offset = Pos - PrevScrollPosition; + break; + } + } + + return (index, offset); + } + */ + private float GetItemStepSize(int index) { if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll) diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs index 93f63a1..e8199eb 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/RecyclerView.cs @@ -88,7 +88,7 @@ namespace Tizen.NUI.Components /// Notify Dataset is Changed. /// [EditorBrowsable(EditorBrowsableState.Never)] - public void NotifyDataSetChanged() + public virtual void NotifyDataSetChanged() { //Need to update view. if (InternalItemsLayouter != null) @@ -113,7 +113,7 @@ namespace Tizen.NUI.Components /// Dataset source. /// Changed item index. [EditorBrowsable(EditorBrowsableState.Never)] - public void NotifyItemChanged(IItemSource source, int startIndex) + public virtual void NotifyItemChanged(IItemSource source, int startIndex) { if (InternalItemsLayouter != null) { @@ -122,76 +122,77 @@ namespace Tizen.NUI.Components } /// - /// Notify observable item is inserted in dataset. + /// Notify range of observable items from start to end are changed. /// /// Dataset source. - /// Inserted item index. + /// Start index of changed items range. + /// End index of changed items range. [EditorBrowsable(EditorBrowsableState.Never)] - public void NotifyItemInserted(IItemSource source, int startIndex) + public virtual void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange) { if (InternalItemsLayouter != null) { - InternalItemsLayouter.NotifyItemInserted(source, startIndex); + InternalItemsLayouter.NotifyItemRangeChanged(source, startRange, endRange); } } /// - /// Notify observable item is moved from fromPosition to ToPosition. + /// Notify observable item is inserted in dataset. /// /// Dataset source. - /// Previous item position. - /// Moved item position. + /// Inserted item index. [EditorBrowsable(EditorBrowsableState.Never)] - public void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition) + public virtual void NotifyItemInserted(IItemSource source, int startIndex) { if (InternalItemsLayouter != null) { - InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition); + InternalItemsLayouter.NotifyItemInserted(source, startIndex); } } /// - /// Notify range of observable items from start to end are changed. + /// Notify count range of observable count items are inserted in startIndex. /// /// Dataset source. - /// Start index of changed items range. - /// End index of changed items range. + /// Start index of inserted items range. + /// The number of inserted items. [EditorBrowsable(EditorBrowsableState.Never)] - public void NotifyItemRangeChanged(IItemSource source, int start, int end) + public virtual void NotifyItemRangeInserted(IItemSource source, int startIndex, int count) { if (InternalItemsLayouter != null) { - InternalItemsLayouter.NotifyItemRangeChanged(source, start, end); + InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count); } } /// - /// Notify count range of observable count items are inserted in startIndex. + /// Notify observable item is moved from fromPosition to ToPosition. /// /// Dataset source. - /// Start index of inserted items range. - /// The number of inserted items. + /// Previous item position. + /// Moved item position. [EditorBrowsable(EditorBrowsableState.Never)] - public void NotifyItemRangeInserted(IItemSource source, int startIndex, int count) + public virtual void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition) { if (InternalItemsLayouter != null) { - InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count); + InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition); } } /// - /// Notify the count range of observable items from the startIndex are removed. + /// Notify the observable item is moved from fromPosition to ToPosition. /// - /// Dataset source. - /// Start index of removed items range. - /// The number of removed items + /// + /// + /// + /// [EditorBrowsable(EditorBrowsableState.Never)] - public void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count) + public virtual void NotifyItemRangeMoved(IItemSource source, int fromPosition, int toPosition, int count) { if (InternalItemsLayouter != null) { - InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count); + InternalItemsLayouter.NotifyItemRangeMoved(source, fromPosition, toPosition, count); } } @@ -201,7 +202,7 @@ namespace Tizen.NUI.Components /// Dataset source. /// Index of removed item. [EditorBrowsable(EditorBrowsableState.Never)] - public void NotifyItemRemoved(IItemSource source, int startIndex) + public virtual void NotifyItemRemoved(IItemSource source, int startIndex) { if (InternalItemsLayouter != null) { @@ -210,6 +211,21 @@ namespace Tizen.NUI.Components } /// + /// Notify the count range of observable items from the startIndex are removed. + /// + /// Dataset source. + /// Start index of removed items range. + /// The number of removed items + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count) + { + if (InternalItemsLayouter != null) + { + InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count); + } + } + + /// /// Realize indexed item. /// /// Index position of realizing item @@ -256,13 +272,11 @@ namespace Tizen.NUI.Components { item.Index = -1; item.ParentItemsView = null; - // Remove BindingContext null set for performance improving. - //item.BindingContext = null; + item.BindingContext = null; item.IsPressed = false; item.IsSelected = false; item.IsEnabled = true; - // Remove Update Style on default for performance improving. - //item.UpdateState(); + item.UpdateState(); item.Relayout -= OnItemRelayout; if (!recycle || !PushRecycleCache(item)) diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewGridSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewGridSample.cs index 36b2992..e9922b3 100755 --- a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewGridSample.cs +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewGridSample.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; using Tizen.NUI.BaseComponents; using Tizen.NUI.Components; using Tizen.NUI.Binding; +using System; namespace Tizen.NUI.Samples { @@ -9,17 +11,25 @@ namespace Tizen.NUI.Samples { CollectionView colView; int itemCount = 500; - int selectedCount; ItemSelectionMode selMode; + ObservableCollection gallerySource; + Gallery insertMenu = new Gallery(999, "Insert item to 3rd"); + Gallery deleteMenu = new Gallery(999, "Delete item at 3rd"); + Gallery moveMenu = new Gallery(999, "Move last item to 3rd"); public void Activate() { Window window = NUIApplication.GetDefaultWindow(); - var myViewModelSource = new GalleryViewModel(itemCount); + var myViewModelSource = gallerySource = new GalleryViewModel(itemCount); + // Add test menu options. + gallerySource.Insert(0, moveMenu); + gallerySource.Insert(0, deleteMenu); + gallerySource.Insert(0, insertMenu); + selMode = ItemSelectionMode.MultipleSelections; DefaultTitleItem myTitle = new DefaultTitleItem(); - myTitle.Text = "Grid Sample Count["+itemCount+"] Selected["+selectedCount+"]"; + myTitle.Text = "Grid Sample Count["+itemCount+"]"; //Set Width Specification as MatchParent to fit the Item width with parent View. myTitle.WidthSpecification = LayoutParamPolicies.MatchParent; @@ -76,7 +86,6 @@ namespace Tizen.NUI.Samples { galItem.Selected = false; Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel); - selectedCount--; } } else continue; @@ -90,15 +99,34 @@ namespace Tizen.NUI.Samples { galItem.Selected = true; Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel); - selectedCount++; + + // Check test menu options. + if (galItem == insertMenu) + { + // Insert new item to index 3. + Random rand = new Random(); + int idx = rand.Next(1000); + gallerySource.Insert(3, new Gallery(idx, "Inserted Item")); + } + else if (galItem == deleteMenu) + { + // Remove item in index 3. + gallerySource.RemoveAt(3); + } + else if (galItem == moveMenu) + { + // Move last indexed item to index 3. + gallerySource.Move(gallerySource.Count - 1, 3); + } } + } else continue; } if (colView.Header != null && colView.Header is DefaultTitleItem) { DefaultTitleItem title = (DefaultTitleItem)colView.Header; - title.Text = "Grid Sample Count["+itemCount+"] Selected["+selectedCount+"]"; + title.Text = "Grid Sample Count["+gallerySource.Count+"] Selected["+newSel.Count+"]"; } } diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewLinearSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewLinearSample.cs index e252842..a43939f 100755 --- a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewLinearSample.cs +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/CollectionViewLinearSample.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; using Tizen.NUI.BaseComponents; using Tizen.NUI.Components; using Tizen.NUI.Binding; @@ -13,12 +14,22 @@ namespace Tizen.NUI.Samples int itemCount = 500; string selectedItem; ItemSelectionMode selMode; + ObservableCollection gallerySource; + Gallery insertMenu = new Gallery(999, "Insert item to 3rd"); + Gallery deleteMenu = new Gallery(999, "Delete item at 3rd"); + Gallery moveMenu = new Gallery(999, "Move last item to 3rd"); + public void Activate() { Window window = NUIApplication.GetDefaultWindow(); - var myViewModelSource = new GalleryViewModel(itemCount); + var myViewModelSource = gallerySource = new GalleryViewModel(itemCount); + // Add test menu options. + gallerySource.Insert(0, moveMenu); + gallerySource.Insert(0, deleteMenu); + gallerySource.Insert(0, insertMenu); + selMode = ItemSelectionMode.SingleSelection; DefaultTitleItem myTitle = new DefaultTitleItem(); myTitle.Text = "Linear Sample Count["+itemCount+"]"; @@ -95,16 +106,36 @@ namespace Tizen.NUI.Samples { if (item == null) break; Gallery selItem = (Gallery)item; - selItem.Selected = true; + //selItem.Selected = true; selectedItem = selItem.Name; + + // Check test menu options. + if (selItem == insertMenu) + { + // Insert new item to index 3. + Random rand = new Random(); + int idx = rand.Next(1000); + gallerySource.Insert(3, new Gallery(idx, "Inserted Item")); + } + else if (selItem == deleteMenu) + { + // Remove item in index 3. + gallerySource.RemoveAt(3); + } + else if (selItem == moveMenu) + { + // Move last indexed item to index 3. + gallerySource.Move(gallerySource.Count - 1, 3); + } //Tizen.Log.Debug("NUI", "LSH :: Selected: {0}", selItem.ViewLabel); } if (colView.Header != null && colView.Header is DefaultTitleItem) { DefaultTitleItem title = (DefaultTitleItem)colView.Header; - title.Text = "Linear Sample Count[" + itemCount + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]"); + title.Text = "Linear Sample Count[" + gallerySource.Count + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]"); } } + public void Deactivate() { if (colView != null) diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Gallery.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Gallery.cs index 4a08d75..c5a749d 100644 --- a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Gallery.cs +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Gallery.cs @@ -7,7 +7,6 @@ using Tizen.NUI.Components; using Tizen.NUI.Binding; - class Gallery : INotifyPropertyChanged { string sourceDir = Tizen.NUI.Samples.CommonResource.GetDaliResourcePath()+"ItemViewDemo/gallery/gallery-medium-"; @@ -75,6 +74,7 @@ class Album : ObservableCollection private int index; private string name; private DateTime date; + private bool selected; public Album(int albumIndex, string albumName, DateTime albumDate) { @@ -98,6 +98,16 @@ class Album : ObservableCollection return date.ToLongDateString(); } } + public bool Selected { + get + { + return selected; + } + set + { + selected = value; + } + } } class GalleryViewModel : ObservableCollection @@ -184,7 +194,6 @@ class AlbumViewModel : ObservableCollection ("Graduation", new DateTime(2017, 6, 30)), }; - public AlbumViewModel() { CreateData(this); diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewGridGroupSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewGridGroupSample.cs index 5aa0b9c..eb104e0 100644 --- a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewGridGroupSample.cs +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewGridGroupSample.cs @@ -11,24 +11,35 @@ namespace Tizen.NUI.Samples public class CollectionViewGridGroupSample : IExample { CollectionView colView; - int selectedCount; ItemSelectionMode selMode; - ObservableCollection groupSource; + ObservableCollection albumSource; + Album insertDeleteGroup = new Album(999, "Insert / Delete Groups", new DateTime(1999, 12, 31)); + Gallery insertMenu = new Gallery(999, "Insert item to first of 3rd Group"); + Gallery deleteMenu = new Gallery(999, "Delete first item in 3rd Group"); + Album moveGroup = new Album(999, "move Groups", new DateTime(1999, 12, 31)); + Gallery moveMenu = new Gallery(999, "Move last item to first in 3rd group"); public void Activate() { Window window = NUIApplication.GetDefaultWindow(); - groupSource = new AlbumViewModel(); + albumSource = new AlbumViewModel(); + // Add test menu options. + moveGroup.Add(moveMenu); + albumSource.Insert(0, moveGroup); + insertDeleteGroup.Add(insertMenu); + insertDeleteGroup.Add(deleteMenu); + albumSource.Insert(0, insertDeleteGroup); + selMode = ItemSelectionMode.MultipleSelections; DefaultTitleItem myTitle = new DefaultTitleItem(); - myTitle.Text = "Grid Sample Count["+ groupSource.Count+"] Selected["+selectedCount+"]"; + myTitle.Text = "Grid Sample Count["+ albumSource.Count+"]"; //Set Width Specification as MatchParent to fit the Item width with parent View. myTitle.WidthSpecification = LayoutParamPolicies.MatchParent; colView = new CollectionView() { - ItemsSource = groupSource, + ItemsSource = albumSource, ItemsLayouter = new GridLayouter(), ItemTemplate = new DataTemplate(() => { @@ -90,11 +101,21 @@ namespace Tizen.NUI.Samples if (!(newSel.Contains(item))) { galItem.Selected = false; - Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel); - selectedCount--; + // Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel); + } + } + else if (item is Album selAlbum) + { + if (!(newSel.Contains(selAlbum))) + { + selAlbum.Selected = false; + // Tizen.Log.Debug("Unselected Group: {0}", selAlbum.Title); + if (selAlbum == insertDeleteGroup) + { + albumSource.RemoveAt(2); + } } } - else continue; } foreach (object item in newSel) { @@ -104,16 +125,58 @@ namespace Tizen.NUI.Samples if (!(oldSel.Contains(item))) { galItem.Selected = true; - Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel); - selectedCount++; + // Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel); + + if (galItem == insertMenu) + { + // Insert new item to index 3. + Random rand = new Random(); + int idx = rand.Next(1000); + albumSource[2].Insert(3, new Gallery(idx, "Inserted Item")); + } + else if (galItem == deleteMenu) + { + // Remove item in index 3. + albumSource[2].RemoveAt(0); + } + else if (galItem == moveMenu) + { + // Move last indexed item to index 3. + albumSource[2].Move(albumSource[2].Count - 1, 0); + } + } + } + else if (item is Album selAlbum) + { + if (!(oldSel.Contains(selAlbum))) + { + selAlbum.Selected = true; + // Tizen.Log.Debug("Selected Group: {0}", selAlbum.Title); + if (selAlbum == insertDeleteGroup) + { + Random rand = new Random(); + int groupIdx = rand.Next(1000); + Album insertAlbum = new Album(groupIdx, "Inserted group", new DateTime(1999, 12, 31)); + int idx = rand.Next(1000); + insertAlbum.Add(new Gallery(idx, "Inserted Item 1")); + idx = rand.Next(1000); + insertAlbum.Add(new Gallery(idx, "Inserted Item 2")); + idx = rand.Next(1000); + insertAlbum.Add(new Gallery(idx, "Inserted Item 3")); + albumSource.Insert(2, insertAlbum); + } + else if (selAlbum == moveGroup) + { + albumSource.Move(albumSource.Count - 1, 2); + + } } } - else continue; } if (colView.Header != null && colView.Header is DefaultTitleItem) { DefaultTitleItem title = (DefaultTitleItem)colView.Header; - title.Text = "Grid Sample Count["+ groupSource.Count + "] Selected["+selectedCount+"]"; + title.Text = "Grid Sample Count["+ albumSource.Count + "] Selected["+newSel.Count+"]"; } } diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewLinearGroupSample.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewLinearGroupSample.cs index f8a759b..f9c63db 100644 --- a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewLinearGroupSample.cs +++ b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/CollectionViewDemo/Group/CollectionViewLinearGroupSample.cs @@ -13,32 +13,38 @@ namespace Tizen.NUI.Samples CollectionView colView; string selectedItem; ItemSelectionMode selMode; - ObservableCollection groupSource; + ObservableCollection albumSource; + Album insertDeleteGroup = new Album(999, "Insert / Delete Groups", new DateTime(1999, 12, 31)); + Gallery insertMenu = new Gallery(999, "Insert item to first of 3rd Group"); + Gallery deleteMenu = new Gallery(999, "Delete first item in 3rd Group"); + Album moveGroup = new Album(999, "move Groups", new DateTime(1999, 12, 31)); + Gallery moveMenu = new Gallery(999, "Move last item to first in 3rd group"); public void Activate() { Window window = NUIApplication.GetDefaultWindow(); - groupSource = new AlbumViewModel(); + albumSource = new AlbumViewModel(); + // Add test menu options. + moveGroup.Add(moveMenu); + albumSource.Insert(0, moveGroup); + insertDeleteGroup.Add(insertMenu); + insertDeleteGroup.Add(deleteMenu); + albumSource.Insert(0, insertDeleteGroup); + selMode = ItemSelectionMode.SingleSelection; DefaultTitleItem myTitle = new DefaultTitleItem(); //To Bind the Count property changes, need to create custom property for count. - myTitle.Text = "Linear Sample Group["+ groupSource.Count+"]"; + myTitle.Text = "Linear Sample Group["+ albumSource.Count+"]"; //Set Width Specification as MatchParent to fit the Item width with parent View. myTitle.WidthSpecification = LayoutParamPolicies.MatchParent; colView = new CollectionView() { - ItemsSource = groupSource, + ItemsSource = albumSource, ItemsLayouter = new LinearLayouter(), ItemTemplate = new DataTemplate(() => { - var rand = new Random(); - RecyclerViewItem item = new RecyclerViewItem(); - item.WidthSpecification = LayoutParamPolicies.MatchParent; - item.HeightSpecification = 100; - item.BackgroundColor = new Color((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble(), 1); - /* DefaultLinearItem item = new DefaultLinearItem(); //Set Width Specification as MatchParent to fit the Item width with parent View. item.WidthSpecification = LayoutParamPolicies.MatchParent; @@ -59,25 +65,19 @@ namespace Tizen.NUI.Samples //item.Extra.SetBinding(RadioButton.IsSelectedProperty, "Selected"); item.Extra.WidthSpecification = 80; item.Extra.HeightSpecification = 80; - */ + return item; }), GroupHeaderTemplate = new DataTemplate(() => { - var rand = new Random(); - RecyclerViewItem item = new RecyclerViewItem(); - item.WidthSpecification = LayoutParamPolicies.MatchParent; - item.HeightSpecification = 50; - item.BackgroundColor = new Color(0, 0, 0, 1); - /* DefaultTitleItem group = new DefaultTitleItem(); //Set Width Specification as MatchParent to fit the Item width with parent View. group.WidthSpecification = LayoutParamPolicies.MatchParent; group.Label.SetBinding(TextLabel.TextProperty, "Date"); group.Label.HorizontalAlignment = HorizontalAlignment.Begin; - */ - return item; + + return group; }), Header = myTitle, IsGrouped = true, @@ -100,26 +100,82 @@ namespace Tizen.NUI.Samples foreach (object item in ev.PreviousSelection) { if (item == null) break; - Gallery unselItem = (Gallery)item; - - unselItem.Selected = false; - selectedItem = null; - //Tizen.Log.Debug("NUI", "LSH :: Unselected: {0}", unselItem.ViewLabel); + if (item is Gallery unselItem) + { + unselItem.Selected = false; + selectedItem = null; + //Tizen.Log.Debug("NUI", "LSH :: Unselected: {0}", unselItem.ViewLabel); + } + else if (item is Album selAlbum) + { + selectedItem = selAlbum.Title; + selAlbum.Selected = false; + if (selAlbum == insertDeleteGroup) + { + albumSource.RemoveAt(2); + } + } + } foreach (object item in ev.CurrentSelection) { if (item == null) break; - Gallery selItem = (Gallery)item; - selItem.Selected = true; - selectedItem = selItem.Name; - //Tizen.Log.Debug("NUI", "LSH :: Selected: {0}", selItem.ViewLabel); + if (item is Gallery selItem) + { + selItem.Selected = true; + selectedItem = selItem.Name; + //Tizen.Log.Debug("NUI", "LSH :: Selected: {0}", selItem.ViewLabel); + + // Check test menu options. + if (selItem == insertMenu) + { + // Insert new item to index 3. + Random rand = new Random(); + int idx = rand.Next(1000); + albumSource[2].Insert(0, new Gallery(idx, "Inserted Item")); + } + else if (selItem == deleteMenu) + { + // Remove item in index 3. + albumSource[2].RemoveAt(0); + } + else if (selItem == moveMenu) + { + // Move last indexed item to index 3. + albumSource[2].Move(albumSource[2].Count - 1, 0); + } + } + else if (item is Album selAlbum) + { + selectedItem = selAlbum.Title; + selAlbum.Selected = true; + if (selAlbum == insertDeleteGroup) + { + Random rand = new Random(); + int groupIdx = rand.Next(1000); + Album insertAlbum = new Album(groupIdx, "Inserted group", new DateTime(1999, 12, 31)); + int idx = rand.Next(1000); + insertAlbum.Add(new Gallery(idx, "Inserted Item 1")); + idx = rand.Next(1000); + insertAlbum.Add(new Gallery(idx, "Inserted Item 2")); + idx = rand.Next(1000); + insertAlbum.Add(new Gallery(idx, "Inserted Item 3")); + albumSource.Insert(2, insertAlbum); + } + else if (selAlbum == moveGroup) + { + albumSource.Move(albumSource.Count - 1, 2); + + } + } } if (colView.Header != null && colView.Header is DefaultTitleItem) { DefaultTitleItem title = (DefaultTitleItem)colView.Header; - title.Text = "Linear Sample Count[" + groupSource + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]"); + title.Text = "Linear Sample Count[" + albumSource.Count + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]"); } } + public void Deactivate() { if (colView != null)