* Introduce CollectionView and related classes.
View :
[ItemsView] Base class of model based view. create item by DataTemplate and
layouting them with layouter using source data. inherited from ScrollableBase.
[CollectionView] groupable and selectable ItemsView.
Item :
[ViewItem] Base item for CollectionView.
[ViewItem.Internal] Internal partial class of ViewItem.
[ViewItemStyle] Style class of ViewItem.
[OneLineLinearItem] Linear type ViewItem of 1 line text Label and Icon, Extra View.
[OutLineGridItem] Grid type ViewItem of outline caption Label and ImageView Image and View Badge.
Layouter :
[ItemsLayouter] Base compositor class of item layouter on ItemsView.
[LinearLayouter] Layouter class of layouting item in linear list. restricted for CollectionView class only.
[GridLayouter] Layouter class of layouting item in grid list. restricted for CollectionView class only.
Item Source :
[ItemsSourceFactory] Factory of creating IItemSource from IEnumerable source.
[IItemSource] Interface for encapsulated data source from IEnumerable user source.
[IGroupedItemSource] Interface for grouped user data.
[EmptySource] Empty data source.
[ListSource] List data source.
[ObservableItemSource] Item source who provide observable notifications.
[ObservableGroupedSource] Grouped item source who provide observable notifications.
[UngroupedItemSource] Ungrouped item source.
Enum and Interface :
[ICollectionChangedNotifier] Interface of collection changed notifier.
[ItemSelectionMode] Mode enum value for Selection.
[ItemSizingStrategy] Sizing strategy for item measure.
[SelectionChangedEventArgs] Event for Selection changed notify.
Helper Class :
[MarshalingObservableCollection] Marshaling ObservableCollection for itemSource.
[SelectionList] Internal list for SelectionModel.
Other Changes :
Moving and change internal to public of DataTemplate related classes.
Known Issues :
- Empty items or pages shows when scroll fast, jump by ScrollTo().
- ScrollTo() is moving incorrectly.
- Scrolling event passing scrollPosition negative value.
- Application cannot initialize the cached ViewItem.
- Image trembling while scrolling.
Remaining Works :
- Implement groupable features.
- Implement observable changes(ICollectionChangedNotifier).
- Implement dynamic size and dynamic template selector.
- Implement Item / Layouter animations and support custom animations.
- Adding samples and test.
- Adding Unit Test.
- Documents and guides.
Design Documents :
https://code.sec.samsung.net/confluence/display/ENUIFWC/5.+Model-based++User+Interfaces
* [NUI] Update templates to EditorBrowsableState.Never
* [CollectionView] fix wrong initialize layouter and item count which makes missing 0 and last indexed item in Header/Footer exist
* [CollectionView] fix missing call of OnRelayout in base class.
* [CollectionView] Implement Groupable Features and Refactoring Items
1. Implement Groupable features in CollectionView, LinearLayouter,
GridLayouter.
2. Refactoring items and adding styles.
OneLineLinearItem => DefaultLinearItem : SubLabel added.
Use RelativeLayout.
Apply DefaultLinerItemStyle.
OutLineGridItem => DefaultGridItem : Label changed Caption.
Use RelativeLayout.
Support CaptionOrientation.
Apply DefaultGridItemStyle.
DefaultTitleItem is added for Header / GroupHeader.
3. Sample is udpated.
4. Remove _ in the code.
* [CollectionView] Refactoring class directories
- Move all item source related classes in ItemSource
- Move all item class in Item
- Move all layouter class in Layouter
* [NUI] add copy-right license pre-comments
* Introduce CollectionView and related classes.
View :
[ItemsView] Base class of model based view. create item by DataTemplate and
layouting them with layouter using source data. inherited from ScrollableBase.
[CollectionView] groupable and selectable ItemsView.
Item :
[ViewItem] Base item for CollectionView.
[ViewItem.Internal] Internal partial class of ViewItem.
[ViewItemStyle] Style class of ViewItem.
[OneLineLinearItem] Linear type ViewItem of 1 line text Label and Icon, Extra View.
[OutLineGridItem] Grid type ViewItem of outline caption Label and ImageView Image and View Badge.
Layouter :
[ItemsLayouter] Base compositor class of item layouter on ItemsView.
[LinearLayouter] Layouter class of layouting item in linear list. restricted for CollectionView class only.
[GridLayouter] Layouter class of layouting item in grid list. restricted for CollectionView class only.
Item Source :
[ItemsSourceFactory] Factory of creating IItemSource from IEnumerable source.
[IItemSource] Interface for encapsulated data source from IEnumerable user source.
[IGroupedItemSource] Interface for grouped user data.
[EmptySource] Empty data source.
[ListSource] List data source.
[ObservableItemSource] Item source who provide observable notifications.
[ObservableGroupedSource] Grouped item source who provide observable notifications.
[UngroupedItemSource] Ungrouped item source.
Enum and Interface :
[ICollectionChangedNotifier] Interface of collection changed notifier.
[ItemSelectionMode] Mode enum value for Selection.
[ItemSizingStrategy] Sizing strategy for item measure.
[SelectionChangedEventArgs] Event for Selection changed notify.
Helper Class :
[MarshalingObservableCollection] Marshaling ObservableCollection for itemSource.
[SelectionList] Internal list for SelectionModel.
Other Changes :
Moving and change internal to public of DataTemplate related classes.
Known Issues :
- Empty items or pages shows when scroll fast, jump by ScrollTo().
- ScrollTo() is moving incorrectly.
- Scrolling event passing scrollPosition negative value.
- Application cannot initialize the cached ViewItem.
- Image trembling while scrolling.
Remaining Works :
- Implement groupable features.
- Implement observable changes(ICollectionChangedNotifier).
- Implement dynamic size and dynamic template selector.
- Implement Item / Layouter animations and support custom animations.
- Adding samples and test.
- Adding Unit Test.
- Documents and guides.
Design Documents :
https://code.sec.samsung.net/confluence/display/ENUIFWC/5.+Model-based++User+Interfaces
* [NUI] Update templates to EditorBrowsableState.Never
* [CollectionView] fix wrong initialize layouter and item count which makes missing 0 and last indexed item in Header/Footer exist
* [CollectionView] fix missing call of OnRelayout in base class.
* [CollectionView] Implement Groupable Features and Refactoring Items
1. Implement Groupable features in CollectionView, LinearLayouter,
GridLayouter.
2. Refactoring items and adding styles.
OneLineLinearItem => DefaultLinearItem : SubLabel added.
Use RelativeLayout.
Apply DefaultLinerItemStyle.
OutLineGridItem => DefaultGridItem : Label changed Caption.
Use RelativeLayout.
Support CaptionOrientation.
Apply DefaultGridItemStyle.
DefaultTitleItem is added for Header / GroupHeader.
3. Sample is udpated.
4. Remove _ in the code.
* [CollectionView] Refactoring class directories
- Move all item source related classes in ItemSource
- Move all item class in Item
- Move all layouter class in Layouter
* [NUI] add copy-right license pre-comments
* [NUI] Remove Old RecyclerView and Renamed ItemsView to RecyclerView
Old RecyclerView was never published or browsed, so get rid of this class and renamed ItemsView to RecyclerView.
RecyclerVIew/ -> Removed.
ItemsView -> RecyclerView
ViewItem -> RecyclerViewItem
ViewItemStyle -> RecyclerViewItemStyle
fix warnings.
* [NUI] fix build errors and comments.
FishEyeLayoutManager and WearableList no longer supported as recyclerView is removed.
* [NUI] Update Samples
* [NUI] apply cache in group header and footer
* [NUI] re-alive wearable list and move RecyclerView in wearble.
* Introduce CollectionView and related classes.
View :
[ItemsView] Base class of model based view. create item by DataTemplate and
layouting them with layouter using source data. inherited from ScrollableBase.
[CollectionView] groupable and selectable ItemsView.
Item :
[ViewItem] Base item for CollectionView.
[ViewItem.Internal] Internal partial class of ViewItem.
[ViewItemStyle] Style class of ViewItem.
[OneLineLinearItem] Linear type ViewItem of 1 line text Label and Icon, Extra View.
[OutLineGridItem] Grid type ViewItem of outline caption Label and ImageView Image and View Badge.
Layouter :
[ItemsLayouter] Base compositor class of item layouter on ItemsView.
[LinearLayouter] Layouter class of layouting item in linear list. restricted for CollectionView class only.
[GridLayouter] Layouter class of layouting item in grid list. restricted for CollectionView class only.
Item Source :
[ItemsSourceFactory] Factory of creating IItemSource from IEnumerable source.
[IItemSource] Interface for encapsulated data source from IEnumerable user source.
[IGroupedItemSource] Interface for grouped user data.
[EmptySource] Empty data source.
[ListSource] List data source.
[ObservableItemSource] Item source who provide observable notifications.
[ObservableGroupedSource] Grouped item source who provide observable notifications.
[UngroupedItemSource] Ungrouped item source.
Enum and Interface :
[ICollectionChangedNotifier] Interface of collection changed notifier.
[ItemSelectionMode] Mode enum value for Selection.
[ItemSizingStrategy] Sizing strategy for item measure.
[SelectionChangedEventArgs] Event for Selection changed notify.
Helper Class :
[MarshalingObservableCollection] Marshaling ObservableCollection for itemSource.
[SelectionList] Internal list for SelectionModel.
Other Changes :
Moving and change internal to public of DataTemplate related classes.
Known Issues :
- Empty items or pages shows when scroll fast, jump by ScrollTo().
- ScrollTo() is moving incorrectly.
- Scrolling event passing scrollPosition negative value.
- Application cannot initialize the cached ViewItem.
- Image trembling while scrolling.
Remaining Works :
- Implement groupable features.
- Implement observable changes(ICollectionChangedNotifier).
- Implement dynamic size and dynamic template selector.
- Implement Item / Layouter animations and support custom animations.
- Adding samples and test.
- Adding Unit Test.
- Documents and guides.
Design Documents :
https://code.sec.samsung.net/confluence/display/ENUIFWC/5.+Model-based++User+Interfaces
* [NUI] Update templates to EditorBrowsableState.Never
* [CollectionView] fix wrong initialize layouter and item count which makes missing 0 and last indexed item in Header/Footer exist
* [CollectionView] fix missing call of OnRelayout in base class.
* [CollectionView] Implement Groupable Features and Refactoring Items
1. Implement Groupable features in CollectionView, LinearLayouter,
GridLayouter.
2. Refactoring items and adding styles.
OneLineLinearItem => DefaultLinearItem : SubLabel added.
Use RelativeLayout.
Apply DefaultLinerItemStyle.
OutLineGridItem => DefaultGridItem : Label changed Caption.
Use RelativeLayout.
Support CaptionOrientation.
Apply DefaultGridItemStyle.
DefaultTitleItem is added for Header / GroupHeader.
3. Sample is udpated.
4. Remove _ in the code.
* [CollectionView] Refactoring class directories
- Move all item source related classes in ItemSource
- Move all item class in Item
- Move all layouter class in Layouter
* [NUI] add copy-right license pre-comments
* Introduce CollectionView and related classes.
View :
[ItemsView] Base class of model based view. create item by DataTemplate and
layouting them with layouter using source data. inherited from ScrollableBase.
[CollectionView] groupable and selectable ItemsView.
Item :
[ViewItem] Base item for CollectionView.
[ViewItem.Internal] Internal partial class of ViewItem.
[ViewItemStyle] Style class of ViewItem.
[OneLineLinearItem] Linear type ViewItem of 1 line text Label and Icon, Extra View.
[OutLineGridItem] Grid type ViewItem of outline caption Label and ImageView Image and View Badge.
Layouter :
[ItemsLayouter] Base compositor class of item layouter on ItemsView.
[LinearLayouter] Layouter class of layouting item in linear list. restricted for CollectionView class only.
[GridLayouter] Layouter class of layouting item in grid list. restricted for CollectionView class only.
Item Source :
[ItemsSourceFactory] Factory of creating IItemSource from IEnumerable source.
[IItemSource] Interface for encapsulated data source from IEnumerable user source.
[IGroupedItemSource] Interface for grouped user data.
[EmptySource] Empty data source.
[ListSource] List data source.
[ObservableItemSource] Item source who provide observable notifications.
[ObservableGroupedSource] Grouped item source who provide observable notifications.
[UngroupedItemSource] Ungrouped item source.
Enum and Interface :
[ICollectionChangedNotifier] Interface of collection changed notifier.
[ItemSelectionMode] Mode enum value for Selection.
[ItemSizingStrategy] Sizing strategy for item measure.
[SelectionChangedEventArgs] Event for Selection changed notify.
Helper Class :
[MarshalingObservableCollection] Marshaling ObservableCollection for itemSource.
[SelectionList] Internal list for SelectionModel.
Other Changes :
Moving and change internal to public of DataTemplate related classes.
Known Issues :
- Empty items or pages shows when scroll fast, jump by ScrollTo().
- ScrollTo() is moving incorrectly.
- Scrolling event passing scrollPosition negative value.
- Application cannot initialize the cached ViewItem.
- Image trembling while scrolling.
Remaining Works :
- Implement groupable features.
- Implement observable changes(ICollectionChangedNotifier).
- Implement dynamic size and dynamic template selector.
- Implement Item / Layouter animations and support custom animations.
- Adding samples and test.
- Adding Unit Test.
- Documents and guides.
Design Documents :
https://code.sec.samsung.net/confluence/display/ENUIFWC/5.+Model-based++User+Interfaces
* [NUI] Update templates to EditorBrowsableState.Never
* [CollectionView] fix wrong initialize layouter and item count which makes missing 0 and last indexed item in Header/Footer exist
* [CollectionView] fix missing call of OnRelayout in base class.
* [CollectionView] Implement Groupable Features and Refactoring Items
1. Implement Groupable features in CollectionView, LinearLayouter,
GridLayouter.
2. Refactoring items and adding styles.
OneLineLinearItem => DefaultLinearItem : SubLabel added.
Use RelativeLayout.
Apply DefaultLinerItemStyle.
OutLineGridItem => DefaultGridItem : Label changed Caption.
Use RelativeLayout.
Support CaptionOrientation.
Apply DefaultGridItemStyle.
DefaultTitleItem is added for Header / GroupHeader.
3. Sample is udpated.
4. Remove _ in the code.
* [CollectionView] Refactoring class directories
- Move all item source related classes in ItemSource
- Move all item class in Item
- Move all layouter class in Layouter
* [NUI] add copy-right license pre-comments
* [NUI] Remove Old RecyclerView and Renamed ItemsView to RecyclerView
Old RecyclerView was never published or browsed, so get rid of this class and renamed ItemsView to RecyclerView.
RecyclerVIew/ -> Removed.
ItemsView -> RecyclerView
ViewItem -> RecyclerViewItem
ViewItemStyle -> RecyclerViewItemStyle
fix warnings.
* [NUI] fix build errors and comments.
FishEyeLayoutManager and WearableList no longer supported as recyclerView is removed.
* [NUI] Update Samples
* [NUI] apply cache in group header and footer
* [NUI] re-alive wearable list and move RecyclerView in wearble.
Co-authored-by: dongsug-song <35130733+dongsug-song@users.noreply.github.com>
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.Linq;
+using System.Collections;
+using System.Collections.Generic;
+using System.Windows.Input;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// This class provides a View that can layouting items in list and grid with high performance.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class CollectionView : RecyclerView
+ {
+ /// <summary>
+ /// Binding Property of selected item in single selection.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty SelectedItemProperty =
+ BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(CollectionView), null,
+ propertyChanged: (bindable, oldValue, newValue)=>
+ {
+ var colView = (CollectionView)bindable;
+ oldValue = colView.selectedItem;
+ colView.selectedItem = newValue;
+ var args = new SelectionChangedEventArgs(oldValue, newValue);
+
+ foreach (RecyclerViewItem item in colView.ContentContainer.Children.Where((item) => item is RecyclerViewItem))
+ {
+ if (item.BindingContext == null) continue;
+ if (item.BindingContext == oldValue) item.IsSelected = false;
+ else if (item.BindingContext == newValue) item.IsSelected = true;
+ }
+
+ SelectionPropertyChanged(colView, args);
+ },
+ defaultValueCreator: (bindable)=>
+ {
+ var colView = (CollectionView)bindable;
+ return colView.selectedItem;
+ });
+
+ /// <summary>
+ /// Binding Property of selected items list in multiple selection.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty SelectedItemsProperty =
+ BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(CollectionView), null,
+ propertyChanged: (bindable, oldValue, newValue)=>
+ {
+ var colView = (CollectionView)bindable;
+ var oldSelection = colView.selectedItems ?? selectEmpty;
+ //FIXME : CoerceSelectedItems calls only isCreatedByXaml
+ var newSelection = (SelectionList)CoerceSelectedItems(colView, newValue);
+ colView.selectedItems = newSelection;
+ colView.SelectedItemsPropertyChanged(oldSelection, newSelection);
+ },
+ defaultValueCreator: (bindable) =>
+ {
+ var colView = (CollectionView)bindable;
+ colView.selectedItems = colView.selectedItems ?? new SelectionList(colView);
+ return colView.selectedItems;
+ });
+
+ /// <summary>
+ /// Binding Property of selected items list in multiple selection.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty SelectionModeProperty =
+ BindableProperty.Create(nameof(SelectionMode), typeof(ItemSelectionMode), typeof(CollectionView), ItemSelectionMode.None,
+ propertyChanged: (bindable, oldValue, newValue)=>
+ {
+ var colView = (CollectionView)bindable;
+ oldValue = colView.selectionMode;
+ colView.selectionMode = (ItemSelectionMode)newValue;
+ SelectionModePropertyChanged(colView, oldValue, newValue);
+ },
+ defaultValueCreator: (bindable) =>
+ {
+ var colView = (CollectionView)bindable;
+ return colView.selectionMode;
+ });
+
+
+ private static readonly IList<object> selectEmpty = new List<object>(0);
+ private DataTemplate itemTemplate = null;
+ private IEnumerable itemsSource = null;
+ private ItemsLayouter itemsLayouter = null;
+ private DataTemplate groupHeaderTemplate;
+ private DataTemplate groupFooterTemplate;
+ private bool isGrouped;
+ private bool wasRelayouted = false;
+ private bool needInitalizeLayouter = false;
+ private object selectedItem;
+ private SelectionList selectedItems;
+ private bool suppressSelectionChangeNotification;
+ private ItemSelectionMode selectionMode = ItemSelectionMode.None;
+ private RecyclerViewItem header;
+ private RecyclerViewItem footer;
+ private View focusedView;
+ private int prevFocusedDataIndex = 0;
+ private List<RecyclerViewItem> recycleGroupHeaderCache { get; } = new List<RecyclerViewItem>();
+ private List<RecyclerViewItem> recycleGroupFooterCache { get; } = new List<RecyclerViewItem>();
+
+ /// <summary>
+ /// Base constructor.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public CollectionView() : base()
+ {
+ FocusGroup = true;
+ SetKeyboardNavigationSupport(true);
+ }
+
+ /// <summary>
+ /// Base constructor with ItemsSource
+ /// </summary>
+ /// <param name="itemsSource">item's data source</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public CollectionView(IEnumerable itemsSource) : this()
+ {
+ ItemsSource = itemsSource;
+ }
+
+ /// <summary>
+ /// Base constructor with ItemsSource, ItemsLayouter and ItemTemplate
+ /// </summary>
+ /// <param name="itemsSource">item's data source</param>
+ /// <param name="layouter">item's layout manager</param>
+ /// <param name="template">item's view template with data bindings</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public CollectionView(IEnumerable itemsSource, ItemsLayouter layouter, DataTemplate template) : this()
+ {
+ ItemsSource = itemsSource;
+ ItemTemplate = template;
+ ItemsLayouter = layouter;
+ }
+
+ /// <summary>
+ /// Event of Selection changed.
+ /// old selection list and new selection will be provided.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
+
+ /// <summary>
+ /// Align item in the viewport when ScrollTo() calls.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum ItemScrollTo
+ {
+ /// <summary>
+ /// Scroll to show item in nearest viewport on scroll direction.
+ /// item is above the scroll viewport, item will be came into front,
+ /// item is under the scroll viewport, item will be came into end,
+ /// item is in the scroll viewport, no scroll.
+ /// </summary>
+ Nearest,
+ /// <summary>
+ /// Scroll to show item in start of the viewport.
+ /// </summary>
+ Start,
+ /// <summary>
+ /// Scroll to show item in center of the viewport.
+ /// </summary>
+ Center,
+ /// <summary>
+ /// Scroll to show item in end of the viewport.
+ /// </summary>
+ End,
+ }
+
+ /// <summary>
+ /// Item's source data.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override IEnumerable ItemsSource
+ {
+ get
+ {
+ return itemsSource;
+ }
+ set
+ {
+
+ itemsSource = value;
+ if (value == null)
+ {
+ if (InternalItemSource != null) InternalItemSource.Dispose();
+ //layouter.Clear()
+ return;
+ }
+ if (InternalItemSource != null) InternalItemSource.Dispose();
+ InternalItemSource = ItemsSourceFactory.Create(this);
+
+ if (itemsLayouter == null) return;
+
+ needInitalizeLayouter = true;
+ Init();
+ }
+ }
+
+ /// <summary>
+ /// DataTemplate for items.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override DataTemplate ItemTemplate
+ {
+ get
+ {
+ return itemTemplate;
+ }
+ set
+ {
+ itemTemplate = value;
+ if (value == null)
+ {
+ //layouter.clear()
+ return;
+ }
+
+ needInitalizeLayouter = true;
+ Init();
+ }
+ }
+
+ /// <summary>
+ /// Items Layouter.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual ItemsLayouter ItemsLayouter
+ {
+ get
+ {
+ return itemsLayouter;
+ }
+ set
+ {
+ itemsLayouter = value;
+ base.InternalItemsLayouter = ItemsLayouter;
+ if (value == null)
+ {
+ needInitalizeLayouter = false;
+ return;
+ }
+
+ needInitalizeLayouter = true;
+ Init();
+ }
+ }
+
+ /// <summary>
+ /// Scrolling direction to display items layout.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new Direction ScrollingDirection
+ {
+ get
+ {
+ return base.ScrollingDirection;
+ }
+ set
+ {
+ base.ScrollingDirection = value;
+
+ if (ScrollingDirection == Direction.Horizontal)
+ {
+ ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
+ }
+ else
+ {
+ ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Selected item in single selection.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public object SelectedItem
+ {
+ get => GetValue(SelectedItemProperty);
+ set => SetValue(SelectedItemProperty, value);
+ }
+
+ /// <summary>
+ /// Selected items list in multiple selection.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public IList<object> SelectedItems
+ {
+ get => (IList<object>)GetValue(SelectedItemsProperty);
+ // set => SetValue(SelectedItemsProperty, new SelectionList(this, value));
+ }
+
+ /// <summary>
+ /// Selection mode to handle items selection. See ItemSelectionMode for details.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ItemSelectionMode SelectionMode
+ {
+ get => (ItemSelectionMode)GetValue(SelectionModeProperty);
+ set => SetValue(SelectionModeProperty, value);
+ }
+
+ /// <summary>
+ /// Command of selection changed.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ICommand SelectionChangedCommand { set; get; }
+
+ /// <summary>
+ /// Command parameter of selection changed.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public object SelectionChangedCommandParameter { set; get; }
+
+ /// <summary>
+ /// Size strategy of measuring scroll content. see details in ItemSizingStrategy.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ItemSizingStrategy SizingStrategy { get; set; }
+
+ /// <summary>
+ /// Header item which placed in top-most position.
+ /// note : internal index and count will be increased.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecyclerViewItem Header
+ {
+ get => header;
+ set
+ {
+ if (header != null)
+ {
+ //ContentContainer.Remove(header);
+ Utility.Dispose(header);
+ }
+ if (value != null)
+ {
+ value.Index = 0;
+ value.ParentItemsView = this;
+ value.IsHeader = true;
+ ContentContainer.Add(value);
+ }
+ header = value;
+ needInitalizeLayouter = true;
+ Init();
+ }
+ }
+
+ /// <summary>
+ /// Footer item which placed in bottom-most position.
+ /// note : internal count will be increased.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecyclerViewItem Footer
+ {
+ get => footer;
+ set
+ {
+ if (footer != null)
+ {
+ //ContentContainer.Remove(footer);
+ Utility.Dispose(footer);
+ }
+ if (value != null)
+ {
+ value.Index = InternalItemSource?.Count ?? 0;
+ value.ParentItemsView = this;
+ value.IsFooter = true;
+ ContentContainer.Add(value);
+ }
+ footer = value;
+ needInitalizeLayouter = true;
+ Init();
+ }
+ }
+
+ /// <summary>
+ /// Boolean flag of group feature existence.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsGrouped
+ {
+ get => isGrouped;
+ set
+ {
+ isGrouped = value;
+ needInitalizeLayouter = true;
+ //Need to re-intialize Internal Item Source.
+ if (InternalItemSource != null)
+ {
+ InternalItemSource.Dispose();
+ InternalItemSource = null;
+ }
+ if (ItemsSource != null)
+ InternalItemSource = ItemsSourceFactory.Create(this);
+ Init();
+ }
+ }
+
+ /// <summary>
+ /// DataTemplate of group header. Group feature is not supported yet.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DataTemplate GroupHeaderTemplate
+ {
+ get
+ {
+ return groupHeaderTemplate;
+ }
+ set
+ {
+ groupHeaderTemplate = value;
+ needInitalizeLayouter = true;
+ Init();
+ }
+ }
+
+ /// <summary>
+ /// DataTemplate of group footer. Group feature is not supported yet.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DataTemplate GroupFooterTemplate
+ {
+ get
+ {
+ return groupFooterTemplate;
+ }
+ set
+ {
+ groupFooterTemplate = value;
+ needInitalizeLayouter = true;
+ Init();
+ }
+ }
+
+
+ /// <summary>
+ /// Internal encapsulated items data source.
+ /// </summary>
+ internal new IGroupableItemSource InternalItemSource
+ {
+ get
+ {
+ return (base.InternalItemSource as IGroupableItemSource);
+ }
+ set
+ {
+ base.InternalItemSource = value;
+ }
+ }
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void OnRelayout(Vector2 size, RelayoutContainer container)
+ {
+ base.OnRelayout(size, container);
+
+ wasRelayouted = true;
+ if (needInitalizeLayouter) Init();
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ {
+ View nextFocusedView = null;
+
+ if (focusedView == null)
+ {
+ // If focusedView is null, find child which has previous data index
+ if (ContentContainer.Children.Count > 0 && InternalItemSource.Count > 0)
+ {
+ for (int i = 0; i < ContentContainer.Children.Count; i++)
+ {
+ RecyclerViewItem item = Children[i] as RecyclerViewItem;
+ if (item?.Index == prevFocusedDataIndex)
+ {
+ nextFocusedView = item;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ // If this is not first focus, request next focus to Layouter
+ nextFocusedView = ItemsLayouter.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
+ }
+
+ if (nextFocusedView != null)
+ {
+ // Check next focused view is inside of visible area.
+ // If it is not, move scroll position to make it visible.
+ Position scrollPosition = ContentContainer.CurrentPosition;
+ float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
+
+ float left = nextFocusedView.Position.X;
+ float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
+ float top = nextFocusedView.Position.Y;
+ float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
+
+ float visibleRectangleLeft = -scrollPosition.X;
+ float visibleRectangleRight = -scrollPosition.X + Size.Width;
+ float visibleRectangleTop = -scrollPosition.Y;
+ float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
+
+ if (ScrollingDirection == Direction.Horizontal)
+ {
+ if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
+ {
+ targetPosition = left;
+ }
+ else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
+ {
+ targetPosition = right - Size.Width;
+ }
+ }
+ else
+ {
+ if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
+ {
+ targetPosition = top;
+ }
+ else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
+ {
+ targetPosition = bottom - Size.Height;
+ }
+ }
+
+ focusedView = nextFocusedView;
+ prevFocusedDataIndex = (nextFocusedView as RecyclerViewItem)?.Index ?? -1;
+
+ ScrollTo(targetPosition, true);
+ }
+ else
+ {
+ // If nextView is null, it means that we should move focus to outside of Control.
+ // Return FocusableView depending on direction.
+ switch (direction)
+ {
+ case View.FocusDirection.Left:
+ {
+ nextFocusedView = LeftFocusableView;
+ break;
+ }
+ case View.FocusDirection.Right:
+ {
+ nextFocusedView = RightFocusableView;
+ break;
+ }
+ case View.FocusDirection.Up:
+ {
+ nextFocusedView = UpFocusableView;
+ break;
+ }
+ case View.FocusDirection.Down:
+ {
+ nextFocusedView = DownFocusableView;
+ break;
+ }
+ }
+
+ if(nextFocusedView != null)
+ {
+ focusedView = null;
+ }
+ else
+ {
+ //If FocusableView doesn't exist, not move focus.
+ nextFocusedView = focusedView;
+ }
+ }
+
+ return nextFocusedView;
+ }
+
+ /// <summary>
+ /// Update selected items list in multiple selection.
+ /// </summary>
+ /// <param name="newSelection">updated selection list by user</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void UpdateSelectedItems(IList<object> newSelection)
+ {
+ var oldSelection = new List<object>(SelectedItems);
+
+ suppressSelectionChangeNotification = true;
+
+ SelectedItems.Clear();
+
+ if (newSelection?.Count > 0)
+ {
+ for (int n = 0; n < newSelection.Count; n++)
+ {
+ SelectedItems.Add(newSelection[n]);
+ }
+ }
+
+ suppressSelectionChangeNotification = false;
+
+ SelectedItemsPropertyChanged(oldSelection, newSelection);
+ }
+
+ /// <summary>
+ /// Scroll to specific position with or without animation.
+ /// </summary>
+ /// <param name="position">Destination.</param>
+ /// <param name="animate">Scroll with or without animation</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new void ScrollTo(float position, bool animate) => base.ScrollTo(position, animate);
+
+ /// <summary>
+ /// Scroll to specific item's aligned position with or without animation.
+ /// </summary>
+ /// <param name="item">Target item of dataset.</param>
+ /// <param name="animate">Boolean flag of animation.</param>
+ /// <param name="align">Align state of item. see details in ItemScrollTo.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void ScrollTo(object item, bool animate = false, ItemScrollTo align = ItemScrollTo.Nearest)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ if (ItemsLayouter == null) throw new Exception("Item Layouter must exist.");
+
+ if (InternalItemSource.GetPosition(item) == -1)
+ {
+ throw new Exception("ScrollTo parameter item is not a member of ItemsSource");
+ }
+
+ float scrollPos, curPos, curSize, curItemSize;
+ (float x, float y) = ItemsLayouter.GetItemPosition(item);
+ (float width, float height) = ItemsLayouter.GetItemSize(item);
+ if (ScrollingDirection == Direction.Horizontal)
+ {
+ scrollPos = x;
+ curPos = ScrollPosition.X;
+ curSize = Size.Width;
+ curItemSize = width;
+ }
+ else
+ {
+ scrollPos = y;
+ curPos = ScrollPosition.Y;
+ curSize = Size.Height;
+ curItemSize = height;
+ }
+
+ //Console.WriteLine("[NUI] ScrollTo [{0}:{1}], curPos{2}, itemPos{3}, curSize{4}, itemSize{5}", InternalItemSource.GetPosition(item), align, curPos, scrollPos, curSize, curItemSize);
+ switch (align)
+ {
+ case ItemScrollTo.Start:
+ //nothing necessary.
+ break;
+ case ItemScrollTo.Center:
+ scrollPos = scrollPos - (curSize / 2) + (curItemSize / 2);
+ break;
+ case ItemScrollTo.End:
+ scrollPos = scrollPos - curSize + curItemSize;
+ break;
+ case ItemScrollTo.Nearest:
+ if (scrollPos < curPos - curItemSize)
+ {
+ // item is placed before the current screen. scrollTo.Top
+ }
+ else if (scrollPos >= curPos + curSize + curItemSize)
+ {
+ // item is placed after the current screen. scrollTo.End
+ scrollPos = scrollPos - curSize + curItemSize;
+ }
+ else
+ {
+ // item is in the scroller. ScrollTo() is ignored.
+ return;
+ }
+ break;
+ }
+
+ //Console.WriteLine("[NUI] ScrollTo [{0}]-------------------", scrollPos);
+ base.ScrollTo(scrollPos, animate);
+ }
+
+ // Realize and Decorate the item.
+ internal override RecyclerViewItem RealizeItem(int index)
+ {
+ if (index == 0 && Header != null)
+ {
+ Header.Show();
+ return Header;
+ }
+
+ if (index == InternalItemSource.Count - 1 && Footer != null)
+ {
+ Footer.Show();
+ return Footer;
+ }
+
+ if (isGrouped)
+ {
+ var context = InternalItemSource.GetItem(index);
+ if (InternalItemSource.IsGroupHeader(index))
+ {
+ DataTemplate templ = (groupHeaderTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupHeaderTemplate;
+
+ RecyclerViewItem groupHeader = PopRecycleGroupCache(templ, true);
+ if (groupHeader == null)
+ {
+ groupHeader = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupHeaderTemplate, context, this);
+
+ groupHeader.ParentItemsView = this;
+ groupHeader.Template = templ;
+ groupHeader.isGroupHeader = true;
+ groupHeader.isGroupFooter = false;
+ ContentContainer.Add(groupHeader);
+ }
+ groupHeader.Index = index;
+ groupHeader.ParentGroup = context;
+ groupHeader.BindingContext = context;
+ //group selection?
+ return groupHeader;
+ }
+ else if (InternalItemSource.IsGroupFooter(index))
+ {
+ DataTemplate templ = (groupFooterTemplate as DataTemplateSelector)?.SelectDataTemplate(context, this) ?? groupFooterTemplate;
+
+ RecyclerViewItem groupFooter = PopRecycleGroupCache(templ, false);
+ if (groupFooter == null)
+ {
+ groupFooter = (RecyclerViewItem)DataTemplateExtensions.CreateContent(groupFooterTemplate, context, this);
+
+ groupFooter.ParentItemsView = this;
+ groupFooter.Template = templ;
+ groupFooter.isGroupHeader = false;
+ groupFooter.isGroupFooter = true;
+ ContentContainer.Add(groupFooter);
+ }
+ groupFooter.Index = index;
+ groupFooter.ParentGroup = context;
+ groupFooter.BindingContext = context;
+
+ //group selection?
+ return groupFooter;
+ }
+ }
+
+ RecyclerViewItem item = base.RealizeItem(index);
+ if (isGrouped) item.ParentGroup = InternalItemSource.GetGroupParent(index);
+
+ switch (SelectionMode)
+ {
+ case ItemSelectionMode.SingleSelection:
+ if (item.BindingContext == SelectedItem) item.IsSelected = true;
+ break;
+
+ case ItemSelectionMode.MultipleSelections:
+ if (SelectedItems?.Contains(item.BindingContext) ?? false) item.IsSelected = true;
+ break;
+ case ItemSelectionMode.None:
+ item.IsSelectable = false;
+ break;
+ }
+
+ return item;
+ }
+
+ // Unrealize and caching the item.
+ internal override void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
+ {
+ if (item == Header)
+ {
+ item.Hide();
+ return;
+ }
+ if (item == Footer)
+ {
+ item.Hide();
+ return;
+ }
+ if (item.isGroupHeader || item.isGroupFooter)
+ {
+ if (!recycle || !PushRecycleGroupCache(item))
+ Utility.Dispose(item);
+ return;
+ }
+
+ item.IsSelected = false;
+ base.UnrealizeItem(item, recycle);
+ }
+
+ internal void SelectedItemsPropertyChanged(IList<object> oldSelection, IList<object> newSelection)
+ {
+ if (suppressSelectionChangeNotification)
+ {
+ return;
+ }
+
+ foreach (RecyclerViewItem item in ContentContainer.Children.Where((item) => item is RecyclerViewItem))
+ {
+ if (item.BindingContext == null) continue;
+ if (newSelection.Contains(item.BindingContext))
+ {
+ if (!item.IsSelected) item.IsSelected = true;
+ }
+ else
+ {
+ if (item.IsSelected) item.IsSelected = false;
+ }
+ }
+ SelectionPropertyChanged(this, new SelectionChangedEventArgs(oldSelection, newSelection));
+
+ OnPropertyChanged(SelectedItemsProperty.PropertyName);
+ }
+
+ /// <summary>
+ /// Internal selection callback.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void OnSelectionChanged(SelectionChangedEventArgs args)
+ {
+ //Selection Callback
+ }
+
+ /// <summary>
+ /// Adjust scrolling position by own scrolling rules.
+ /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
+ /// </summary>
+ /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
+ /// <returns>Adjusted scroll destination</returns>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override float AdjustTargetPositionOfScrollAnimation(float position)
+ {
+ // Destination is depending on implementation of layout manager.
+ // Get destination from layout manager.
+ return ItemsLayouter.CalculateCandidateScrollPosition(position);
+ }
+
+ /// <summary>
+ /// OnScroll event callback.
+ /// </summary>
+ /// <param name="source">Scroll source object</param>
+ /// <param name="args">Scroll event argument</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void OnScrolling(object source, ScrollEventArgs args)
+ {
+ if (disposed) return;
+
+ if (needInitalizeLayouter)
+ {
+ ItemsLayouter.Initialize(this);
+ needInitalizeLayouter = false;
+ }
+ base.OnScrolling(source, args);
+ }
+
+ /// <summary>
+ /// Dispose ItemsView and all children on it.
+ /// </summary>
+ /// <param name="type">Dispose type.</param>
+ protected override void Dispose(DisposeTypes type)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (type == DisposeTypes.Explicit)
+ {
+ disposed = true;
+ if (InternalItemSource != null)
+ {
+ InternalItemSource.Dispose();
+ InternalItemSource = null;
+ }
+ if (Header != null)
+ {
+ Utility.Dispose(Header);
+ Header = null;
+ }
+ if (Footer != null)
+ {
+ Utility.Dispose(Footer);
+ Footer = null;
+ }
+ groupHeaderTemplate = null;
+ groupFooterTemplate = null;
+ //
+ }
+
+ base.Dispose(type);
+ }
+
+ private static void SelectionPropertyChanged(CollectionView colView, SelectionChangedEventArgs args)
+ {
+ var command = colView.SelectionChangedCommand;
+
+ if (command != null)
+ {
+ var commandParameter = colView.SelectionChangedCommandParameter;
+
+ if (command.CanExecute(commandParameter))
+ {
+ command.Execute(commandParameter);
+ }
+ }
+ colView.SelectionChanged?.Invoke(colView, args);
+ colView.OnSelectionChanged(args);
+ }
+
+ private static object CoerceSelectedItems(BindableObject bindable, object value)
+ {
+ if (value == null)
+ {
+ return new SelectionList((CollectionView)bindable);
+ }
+
+ if (value is SelectionList)
+ {
+ return value;
+ }
+
+ return new SelectionList((CollectionView)bindable, value as IList<object>);
+ }
+
+ private static void SelectionModePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var colView = (CollectionView)bindable;
+
+ var oldMode = (ItemSelectionMode)oldValue;
+ var newMode = (ItemSelectionMode)newValue;
+
+ IList<object> previousSelection = new List<object>();
+ IList<object> newSelection = new List<object>();
+
+ switch (oldMode)
+ {
+ case ItemSelectionMode.None:
+ break;
+ case ItemSelectionMode.SingleSelection:
+ if (colView.SelectedItem != null)
+ {
+ previousSelection.Add(colView.SelectedItem);
+ }
+ break;
+ case ItemSelectionMode.MultipleSelections:
+ previousSelection = colView.SelectedItems;
+ break;
+ }
+
+ switch (newMode)
+ {
+ case ItemSelectionMode.None:
+ break;
+ case ItemSelectionMode.SingleSelection:
+ if (colView.SelectedItem != null)
+ {
+ newSelection.Add(colView.SelectedItem);
+ }
+ break;
+ case ItemSelectionMode.MultipleSelections:
+ newSelection = colView.SelectedItems;
+ break;
+ }
+
+ if (previousSelection.Count == newSelection.Count)
+ {
+ if (previousSelection.Count == 0 || (previousSelection[0] == newSelection[0]))
+ {
+ // Both selections are empty or have the same single item; no reason to signal a change
+ return;
+ }
+ }
+
+ var args = new SelectionChangedEventArgs(previousSelection, newSelection);
+ SelectionPropertyChanged(colView, args);
+ }
+
+ private void Init()
+ {
+ if (ItemsSource == null) return;
+ if (ItemsLayouter == null) return;
+ if (ItemTemplate == null) return;
+
+ if (disposed) return;
+ if (needInitalizeLayouter)
+ {
+ if (InternalItemSource == null) return;
+
+ InternalItemSource.HasHeader = (header != null);
+ InternalItemSource.HasFooter = (footer != null);
+ }
+
+ if (!wasRelayouted) return;
+
+ if (needInitalizeLayouter)
+ {
+ ItemsLayouter.Initialize(this);
+ needInitalizeLayouter = false;
+ }
+ ItemsLayouter.RequestLayout(0.0f, true);
+
+ if (ScrollingDirection == Direction.Horizontal)
+ {
+ ContentContainer.SizeWidth = ItemsLayouter.CalculateLayoutOrientationSize();
+ }
+ else
+ {
+ ContentContainer.SizeHeight = ItemsLayouter.CalculateLayoutOrientationSize();
+ }
+ }
+
+ private bool PushRecycleGroupCache(RecyclerViewItem item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ if (RecycleCache.Count >= 20) return false;
+ if (item.Template == null) return false;
+ if (item.isGroupHeader)
+ {
+ recycleGroupHeaderCache.Add(item);
+ }
+ else if (item.isGroupFooter)
+ {
+ recycleGroupFooterCache.Add(item);
+ }
+ else return false;
+ item.Hide();
+ item.Index = -1;
+ return true;
+ }
+
+ private RecyclerViewItem PopRecycleGroupCache(DataTemplate Template, bool isHeader)
+ {
+ RecyclerViewItem viewItem = null;
+
+ var Cache = (isHeader ? recycleGroupHeaderCache : recycleGroupFooterCache);
+ for (int i = 0; i < Cache.Count; i++)
+ {
+ viewItem = Cache[i];
+ if (Template == viewItem.Template) break;
+ }
+
+ if (viewItem != null)
+ {
+ Cache.Remove(viewItem);
+ viewItem.Show();
+ }
+ return viewItem;
+ }
+
+ }
+}
+++ /dev/null
-/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-using System;
-using Tizen.NUI.BaseComponents;
-using System.Collections.Generic;
-using System.ComponentModel;
-
-namespace Tizen.NUI.Components
-{
- /// <summary>
- /// [Draft] This class implements a grid box layout.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public class GridRecycleLayoutManager : RecycleLayoutManager
- {
- private int rows = 1;
-
- /// <summary>
- /// [draft ]Get/Set the number of rows in the grid
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
- [EditorBrowsable(EditorBrowsableState.Never)]
- public int Rows
- {
- get
- {
- return rows;
- }
- set
- {
- rows = value;
-
- if (Container != null)
- {
- Layout(PrevScrollPosition);
- }
- }
- }
-
- private int columns = 1;
-
-
- /// <summary>
- /// [Draft] Get/Set the number of columns in the grid
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
- [EditorBrowsable(EditorBrowsableState.Never)]
- public int Columns
- {
- get
- {
- return columns;
- }
- set
- {
- columns = value;
-
- if (Container != null)
- {
- Layout(PrevScrollPosition);
- }
- }
- }
-
- private int firstVisibleItemIndex = -1;
- private int lastVisibleItemIndex = -1;
-
- private bool IsItemVisible(float scrollPosition, RecycleItem item)
- {
- bool result = false;
- View list = Container.GetParent() as View;
- if (list == null)
- {
- return result;
- }
- Vector2 visibleArea = new Vector2(Math.Abs(scrollPosition),
- Math.Abs(scrollPosition) + (LayoutOrientation == Orientation.Vertical ?
- list.Size.Width : list.Size.Height)
- );
-
- float firstCheckPoint = LayoutOrientation == Orientation.Vertical ? item.Position.X : item.Position.Y;
- float secondCheckPoint = LayoutOrientation == Orientation.Vertical ?
- firstCheckPoint + item.Size.Width :
- firstCheckPoint + item.Size.Height;
-
- result = (firstCheckPoint >= visibleArea.X && firstCheckPoint <= visibleArea.Y) || (secondCheckPoint >= visibleArea.X && secondCheckPoint <= visibleArea.Y);
-
- return result;
- }
-
- /// <summary>
- /// This is called to find out how much container size can be.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override float CalculateLayoutOrientationSize()
- {
- float orientationFactor = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
- return StepSize * (int)Math.Ceiling((double)DataCount / (double)orientationFactor);
- }
-
-
- /// <summary>
- /// This is called to find out where items are lain out according to current scroll position.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- public override void Layout(float scrollPosition)
- {
- int itemInGroup = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
- firstVisibleItemIndex = -1;
- lastVisibleItemIndex = -1;
-
- RecycleItem previousItem = null;
-
- for (int i = 0; i < Container.Children.Count; i++)
- {
- RecycleItem item = Container.Children[i] as RecycleItem;
-
- if (previousItem != null && item != null)
- {
- item.Position = LayoutOrientation == Orientation.Vertical ?
- new Position(
- (i % itemInGroup == 0 ?
- previousItem.Position.X + (previousItem.CurrentSize.Width != 0 ?
- previousItem.CurrentSize.Width :
- previousItem.Size.Width) :
- previousItem.Position.X),
- (i % itemInGroup == 0 ?
- 0 :
- previousItem.PositionY + (previousItem.CurrentSize.Height != 0 ?
- previousItem.CurrentSize.Height :
- previousItem.Size.Height))
- ) :
- new Position(
- (i % itemInGroup == 0 ?
- 0 :
- previousItem.PositionX + (previousItem.CurrentSize.Width != 0 ?
- previousItem.CurrentSize.Width :
- previousItem.Size.Width)),
- (i % itemInGroup == 0 ?
- previousItem.Position.Y + (previousItem.CurrentSize.Height != 0 ?
- previousItem.CurrentSize.Height :
- previousItem.Size.Height) :
- previousItem.Position.Y)
- );
- }
-
- bool isVisible = IsItemVisible(scrollPosition, item);
-
- if (isVisible)
- {
- firstVisibleItemIndex = firstVisibleItemIndex == -1 ? i : firstVisibleItemIndex;
- lastVisibleItemIndex = i;
- }
-
- previousItem = item;
- }
-
- if (StepSize == 0)
- {
- StepSize = LayoutOrientation == Orientation.Vertical ? ItemSize.Width : ItemSize.Height;
- }
- }
-
-
- /// <summary>
- /// This is called to find out which items should be recycled according to current scroll position.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <returns>List of RecycleItems which should be recycled.</returns>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- public override List<RecycleItem> Recycle(float scrollPosition)
- {
- List<RecycleItem> result = new List<RecycleItem>();
- bool checkFront = (PrevScrollPosition - scrollPosition) > 0;
-
- int itemInGroup = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
-
- if (checkFront)
- {
- int currentGroupNum = (int)(firstVisibleItemIndex / itemInGroup) + 1;
-
- if (currentGroupNum > 2)
- {
- // Too many item is in front!!! move first item to back!!!!
- for (int i = 0; i < itemInGroup; i++)
- {
- RecycleItem target = Container.Children[0] as RecycleItem;
- if (target != null)
- {
- target.DataIndex = target.DataIndex + Container.Children.Count;
- target.SiblingOrder = Container.Children.Count - 1;
-
- result.Add(target);
- }
- }
- }
- }
- else
- {
- int currentGroupNum = (int)(lastVisibleItemIndex / itemInGroup) + 1;
-
- if (currentGroupNum < (int)(Container.Children.Count / itemInGroup) - 3)
- {
- for (int i = 0; i < itemInGroup; i++)
- {
- RecycleItem prevFirstItem = Container.Children[itemInGroup] as RecycleItem;
- RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem;
- if (prevFirstItem != null && target != null)
- {
- target.Position = new Position(
- LayoutOrientation == Orientation.Vertical ? (prevFirstItem.Position.X - target.Size.Width) : prevFirstItem.Position.X,
- LayoutOrientation == Orientation.Vertical ? prevFirstItem.Position.Y : (prevFirstItem.Position.Y - target.Size.Height)
- );
- target.DataIndex = target.DataIndex - Container.Children.Count;
- target.SiblingOrder = 0;
-
- result.Add(target);
- }
- }
- }
- }
-
-
- PrevScrollPosition = scrollPosition;
-
- return result;
- }
-
- /// <summary>
- /// Adjust scrolling position by own scrolling rules.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- public override float CalculateCandidateScrollPosition(float scrollPosition)
- {
- return scrollPosition;
- }
-
- public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
- {
- View nextFocusedView = null;
- int targetSibling = -1;
- bool isHorizontal = LayoutOrientation == Orientation.Horizontal;
-
- switch (direction)
- {
- case View.FocusDirection.Left:
- {
- targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - 1 : currentFocusedView.SiblingOrder - Rows;
- break;
- }
- case View.FocusDirection.Right:
- {
- targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + 1 : currentFocusedView.SiblingOrder + Rows;
- break;
- }
- case View.FocusDirection.Up:
- {
- targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - Columns : currentFocusedView.SiblingOrder - 1;
- break;
- }
- case View.FocusDirection.Down:
- {
- targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + Columns : currentFocusedView.SiblingOrder + 1;
- break;
- }
- }
-
- if (targetSibling > -1 && targetSibling < Container.Children.Count)
- {
- RecycleItem candidate = Container.Children[targetSibling] as RecycleItem;
- if (candidate != null && candidate.DataIndex >= 0 && candidate.DataIndex < DataCount)
- {
- nextFocusedView = candidate;
- }
- }
-
- return nextFocusedView;
- }
- }
-}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// Notify observers about dataset changes of observable items.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface ICollectionChangedNotifier
+ {
+
+ /// <summary>
+ /// Notify the dataset is Changed.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void NotifyDataSetChanged();
+
+ /// <summary>
+ /// Notify the observable item in startIndex is changed.
+ /// </summary>
+ /// <param name="source">dataset source</param>
+ /// <param name="startIndex">changed item index</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void NotifyItemChanged(IItemSource source, int startIndex);
+
+ /// <summary>
+ /// Notify the observable item is inserted in dataset.
+ /// </summary>
+ /// <param name="source">dataset source</param>
+ /// <param name="startIndex">Inserted item index</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void NotifyItemInserted(IItemSource source, int startIndex);
+
+ /// <summary>
+ /// Notify the observable item is moved from fromPosition to ToPosition.
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="fromPosition"></param>
+ /// <param name="toPosition"></param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition);
+
+ /// <summary>
+ /// Notify the range of observable items from start to end are changed.
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="startIndex"></param>
+ /// <param name="endIndex"></param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void NotifyItemRangeChanged(IItemSource source, int startIndex, int endIndex);
+
+ /// <summary>
+ /// Notify the count range of observable items are inserted in startIndex.
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="startIndex"></param>
+ /// <param name="count"></param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void NotifyItemRangeInserted(IItemSource source, int startIndex, int count);
+
+ /// <summary>
+ /// Notify the count range of observable items from the startIndex are removed.
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="startIndex"></param>
+ /// <param name="count"></param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count);
+
+ /// <summary>
+ /// Notify the observable item in startIndex is removed.
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="startIndex"></param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void NotifyItemRemoved(IItemSource source, int startIndex);
+ }
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// DefaultGridItem is one kind of common component, a DefaultGridItem clearly describes what action will occur when the user selects it.
+ /// DefaultGridItem may contain text or an icon.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class DefaultGridItem : RecyclerViewItem
+ {
+ private TextLabel itemCaption;
+ private ImageView itemImage;
+ private View itemBadge;
+ private CaptionOrientation captionOrientation;
+ private bool layoutChanged;
+
+ private DefaultGridItemStyle ItemStyle => ViewStyle as DefaultGridItemStyle;
+
+ /// <summary>
+ /// Return a copied Style instance of DefaultLinearItem
+ /// </summary>
+ /// <remarks>
+ /// It returns copied Style instance and changing it does not effect to the DefaultLinearItem.
+ /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new DefaultGridItemStyle Style
+ {
+ get
+ {
+ var result = new DefaultGridItemStyle(ItemStyle);
+ result.CopyPropertiesFromView(this);
+ if (itemCaption) result.Caption.CopyPropertiesFromView(itemCaption);
+ if (itemImage) result.Image.CopyPropertiesFromView(itemImage);
+ if (itemBadge) result.Badge.CopyPropertiesFromView(itemBadge);
+
+ return result;
+ }
+ }
+
+ static DefaultGridItem() {}
+
+ /// <summary>
+ /// Creates a new instance of DefaultGridItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultGridItem() : base()
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Creates a new instance of DefaultGridItem with style
+ /// </summary>
+ /// <param name="style=">Create DefaultGridItem by special style defined in UX.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultGridItem(string style) : base(style)
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Creates a new instance of DefaultGridItem with style
+ /// </summary>
+ /// <param name="itemStyle=">Create DefaultGridItem by style customized by user.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultGridItem(DefaultGridItemStyle itemStyle) : base(itemStyle)
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Caption orientation.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum CaptionOrientation
+ {
+ /// <summary>
+ /// Outside of image bottom edge.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ OutsideBottom,
+ /// <summary>
+ /// Outside of image top edge.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ OutsideTop,
+ /// <summary>
+ /// inside of image bottom edge.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ InsideBottom,
+ /// <summary>
+ /// inside of image top edge.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ InsideTop,
+ }
+
+ /// <summary>
+ /// DefaultGridItem's icon part.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ImageView Image
+ {
+ get
+ {
+ if ( itemImage == null)
+ {
+ itemImage = CreateImage(ItemStyle.Image);
+ if (itemImage != null)
+ {
+ Add(itemImage);
+ itemImage.Relayout += OnImageRelayout;
+ layoutChanged = true;
+ }
+ }
+ return itemImage;
+ }
+ internal set
+ {
+ itemImage = value;
+ layoutChanged = true;
+ }
+ }
+
+
+ /// <summary>
+ /// DefaultGridItem's badge object. will be placed in right-top edge.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public View Badge
+ {
+ get
+ {
+ return itemBadge;
+
+ }
+ set
+ {
+ if (value == null)
+ {
+ Remove(itemBadge);
+ }
+ itemBadge = value;
+ if (itemBadge != null)
+ {
+ itemBadge.ApplyStyle(ItemStyle.Badge);
+ Add(itemBadge);
+ }
+ layoutChanged = true;
+ }
+ }
+
+/* open when ImageView using Uri not string
+ /// <summary>
+ /// Image image's resource url in DefaultGridItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string ImageUrl
+ {
+ get
+ {
+ return Image.ResourceUrl;
+ }
+ set
+ {
+ Image.ResourceUrl = value;
+ }
+ }
+*/
+
+ /// <summary>
+ /// DefaultGridItem's text part.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TextLabel Caption
+ {
+ get
+ {
+ if (itemCaption == null)
+ {
+ itemCaption = CreateLabel(ItemStyle.Caption);
+ if (itemCaption != null)
+ {
+ Add(itemCaption);
+ layoutChanged = true;
+ }
+ }
+ return itemCaption;
+ }
+ internal set
+ {
+ itemCaption = value;
+ layoutChanged = true;
+ AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Label, itemCaption.Text);
+ }
+ }
+
+ /// <summary>
+ /// The text of DefaultGridItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Text
+ {
+ get
+ {
+ return Caption.Text;
+ }
+ set
+ {
+ Caption.Text = value;
+ }
+ }
+
+ /// <summary>
+ /// Caption relative orientation with image in DefaultGridItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public CaptionOrientation CaptionRelativeOrientation
+ {
+ get
+ {
+ return captionOrientation;
+ }
+ set
+ {
+ captionOrientation = value;
+ layoutChanged = true;
+ }
+ }
+
+ /// <summary>
+ /// Apply style to DefaultLinearItemStyle.
+ /// </summary>
+ /// <param name="viewStyle">The style to apply.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void ApplyStyle(ViewStyle viewStyle)
+ {
+
+ base.ApplyStyle(viewStyle);
+ if (viewStyle != null && viewStyle is DefaultGridItemStyle defaultStyle)
+ {
+ if (itemCaption != null)
+ itemCaption.ApplyStyle(defaultStyle.Caption);
+ if (itemImage != null)
+ itemImage.ApplyStyle(defaultStyle.Image);
+ if (itemBadge != null)
+ itemBadge.ApplyStyle(defaultStyle.Badge);
+ }
+ }
+
+ /// <summary>
+ /// Creates Item's text part.
+ /// </summary>
+ /// <return>The created Item's text part.</return>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual TextLabel CreateLabel(TextLabelStyle textStyle)
+ {
+ return new TextLabel(textStyle)
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ }
+
+ /// <summary>
+ /// Creates Item's icon part.
+ /// </summary>
+ /// <return>The created Item's icon part.</return>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual ImageView CreateImage(ImageViewStyle imageStyle)
+ {
+ return new ImageView(imageStyle);
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void MeasureChild()
+ {
+ //nothing to do.
+ if (itemCaption)
+ {
+ var pad = Padding;
+ var margin = itemCaption.Margin;
+ itemCaption.SizeWidth = SizeWidth - pad.Start - pad.End - margin.Start - margin.End;
+ }
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void LayoutChild()
+ {
+ if (!layoutChanged) return;
+ if (itemImage == null) return;
+
+ layoutChanged = false;
+
+ RelativeLayout.SetLeftTarget(itemImage, this);
+ RelativeLayout.SetLeftRelativeOffset(itemImage, 0.0F);
+ RelativeLayout.SetRightTarget(itemImage, this);
+ RelativeLayout.SetRightRelativeOffset(itemImage, 1.0F);
+ RelativeLayout.SetHorizontalAlignment(itemImage, RelativeLayout.Alignment.Center);
+
+ if (itemCaption != null)
+ {
+ itemCaption.RaiseAbove(itemImage);
+ RelativeLayout.SetLeftTarget(itemCaption, itemImage);
+ RelativeLayout.SetLeftRelativeOffset(itemCaption, 0.0F);
+ RelativeLayout.SetRightTarget(itemCaption, itemImage);
+ RelativeLayout.SetRightRelativeOffset(itemCaption, 1.0F);
+ RelativeLayout.SetHorizontalAlignment(itemCaption, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetFillHorizontal(itemCaption, true);
+ }
+ else
+ {
+ RelativeLayout.SetTopTarget(itemImage, this);
+ RelativeLayout.SetTopRelativeOffset(itemImage, 0.0F);
+ RelativeLayout.SetBottomTarget(itemImage, this);
+ RelativeLayout.SetBottomRelativeOffset(itemImage, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+ }
+
+ if (itemBadge)
+ {
+ RelativeLayout.SetLeftTarget(itemBadge, itemImage);
+ RelativeLayout.SetLeftRelativeOffset(itemBadge, 1.0F);
+ RelativeLayout.SetRightTarget(itemBadge, itemImage);
+ RelativeLayout.SetRightRelativeOffset(itemBadge, 1.0F);
+ RelativeLayout.SetHorizontalAlignment(itemBadge, RelativeLayout.Alignment.End);
+ }
+
+ switch (captionOrientation)
+ {
+ case CaptionOrientation.OutsideBottom:
+ if (itemCaption != null)
+ {
+ RelativeLayout.SetTopTarget(itemCaption, this);
+ RelativeLayout.SetTopRelativeOffset(itemCaption, 1.0F);
+ RelativeLayout.SetBottomTarget(itemCaption, this);
+ RelativeLayout.SetBottomRelativeOffset(itemCaption, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemCaption, RelativeLayout.Alignment.End);
+
+ RelativeLayout.SetTopTarget(itemImage, this);
+ RelativeLayout.SetTopRelativeOffset(itemImage, 0.0F);
+ RelativeLayout.SetBottomTarget(itemImage, itemCaption);
+ RelativeLayout.SetBottomRelativeOffset(itemImage, 0.0F);
+ RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+ }
+
+ if (itemBadge)
+ {
+ RelativeLayout.SetTopTarget(itemBadge, itemImage);
+ RelativeLayout.SetTopRelativeOffset(itemBadge, 0.0F);
+ RelativeLayout.SetBottomTarget(itemBadge, itemImage);
+ RelativeLayout.SetBottomRelativeOffset(itemBadge, 0.0F);
+ RelativeLayout.SetVerticalAlignment(itemBadge, RelativeLayout.Alignment.Start);
+ }
+ break;
+
+ case CaptionOrientation.OutsideTop:
+ if (itemCaption != null)
+ {
+ RelativeLayout.SetTopTarget(itemCaption, this);
+ RelativeLayout.SetTopRelativeOffset(itemCaption, 0.0F);
+ RelativeLayout.SetBottomTarget(itemCaption, this);
+ RelativeLayout.SetBottomRelativeOffset(itemCaption, 0.0F);
+ RelativeLayout.SetVerticalAlignment(itemCaption, RelativeLayout.Alignment.Start);
+
+ RelativeLayout.SetTopTarget(itemImage, itemCaption);
+ RelativeLayout.SetTopRelativeOffset(itemImage, 1.0F);
+ RelativeLayout.SetBottomTarget(itemImage, this);
+ RelativeLayout.SetBottomRelativeOffset(itemImage, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+ }
+
+ if (itemBadge)
+ {
+ RelativeLayout.SetTopTarget(itemBadge, itemImage);
+ RelativeLayout.SetTopRelativeOffset(itemBadge, 1.0F);
+ RelativeLayout.SetBottomTarget(itemBadge, itemImage);
+ RelativeLayout.SetBottomRelativeOffset(itemBadge, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemBadge, RelativeLayout.Alignment.End);
+ }
+ break;
+
+ case CaptionOrientation.InsideBottom:
+ if (itemCaption != null)
+ {
+ RelativeLayout.SetTopTarget(itemCaption, this);
+ RelativeLayout.SetTopRelativeOffset(itemCaption, 1.0F);
+ RelativeLayout.SetBottomTarget(itemCaption, this);
+ RelativeLayout.SetBottomRelativeOffset(itemCaption, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemCaption, RelativeLayout.Alignment.End);
+
+ RelativeLayout.SetTopTarget(itemImage, this);
+ RelativeLayout.SetTopRelativeOffset(itemImage, 0.0F);
+ RelativeLayout.SetBottomTarget(itemImage, this);
+ RelativeLayout.SetBottomRelativeOffset(itemImage, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+ }
+
+ if (itemBadge)
+ {
+ RelativeLayout.SetTopTarget(itemBadge, itemImage);
+ RelativeLayout.SetTopRelativeOffset(itemBadge, 0.0F);
+ RelativeLayout.SetBottomTarget(itemBadge, itemImage);
+ RelativeLayout.SetBottomRelativeOffset(itemBadge, 0.0F);
+ RelativeLayout.SetVerticalAlignment(itemBadge, RelativeLayout.Alignment.Start);
+ }
+ break;
+
+ case CaptionOrientation.InsideTop:
+ if (itemCaption != null)
+ {
+ RelativeLayout.SetTopTarget(itemCaption, this);
+ RelativeLayout.SetTopRelativeOffset(itemCaption, 0.0F);
+ RelativeLayout.SetBottomTarget(itemCaption, this);
+ RelativeLayout.SetBottomRelativeOffset(itemCaption, 0.0F);
+ RelativeLayout.SetVerticalAlignment(itemCaption, RelativeLayout.Alignment.Start);
+
+ RelativeLayout.SetTopTarget(itemImage, this);
+ RelativeLayout.SetTopRelativeOffset(itemImage, 0.0F);
+ RelativeLayout.SetBottomTarget(itemImage, this);
+ RelativeLayout.SetBottomRelativeOffset(itemImage, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemImage, RelativeLayout.Alignment.Center);
+ }
+
+ if (itemBadge)
+ {
+ RelativeLayout.SetTopTarget(itemBadge, itemImage);
+ RelativeLayout.SetTopRelativeOffset(itemBadge, 1.0F);
+ RelativeLayout.SetBottomTarget(itemBadge, itemImage);
+ RelativeLayout.SetBottomRelativeOffset(itemBadge, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemBadge, RelativeLayout.Alignment.End);
+ }
+ break;
+ }
+
+
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ private void Initialize()
+ {
+ Layout = new RelativeLayout();
+ layoutChanged = true;
+ LayoutDirectionChanged += OnLayoutDirectionChanged;
+ }
+
+ /// <summary>
+ /// Dispose Item and all children on it.
+ /// </summary>
+ /// <param name="type">Dispose type.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void Dispose(DisposeTypes type)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (type == DisposeTypes.Explicit)
+ {
+ //Extension : Extension?.OnDispose(this);
+
+ if (itemImage != null)
+ {
+ Utility.Dispose(itemImage);
+ }
+ if (itemCaption != null)
+ {
+ Utility.Dispose(itemCaption);
+ }
+ if (itemBadge != null)
+ {
+ Utility.Dispose(itemBadge);
+ }
+ }
+
+ base.Dispose(type);
+ }
+
+ private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
+ {
+ MeasureChild();
+ LayoutChild();
+ }
+ private void OnImageRelayout(object sender, EventArgs e)
+ {
+ MeasureChild();
+ LayoutChild();
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// DefaultLinearItem is one kind of common component, a DefaultLinearItem clearly describes what action will occur when the user selects it.
+ /// DefaultLinearItem may contain text or an icon.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class DefaultLinearItem : RecyclerViewItem
+ {
+ private View itemIcon;
+ private TextLabel itemLabel;
+ private TextLabel itemSubLabel;
+ private View itemExtra;
+ private View itemSeperator;
+ private bool layoutChanged;
+ private Size prevSize;
+ private DefaultLinearItemStyle ItemStyle => ViewStyle as DefaultLinearItemStyle;
+
+ /// <summary>
+ /// Return a copied Style instance of DefaultLinearItem
+ /// </summary>
+ /// <remarks>
+ /// It returns copied Style instance and changing it does not effect to the DefaultLinearItem.
+ /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new DefaultLinearItemStyle Style
+ {
+ get
+ {
+ var result = new DefaultLinearItemStyle(ItemStyle);
+ result.CopyPropertiesFromView(this);
+ if (itemLabel) result.Label.CopyPropertiesFromView(itemLabel);
+ if (itemSubLabel) result.SubLabel.CopyPropertiesFromView(itemSubLabel);
+ if (itemIcon) result.Icon.CopyPropertiesFromView(itemIcon);
+ if (itemExtra) result.Extra.CopyPropertiesFromView(itemExtra);
+ if (itemSeperator) result.Seperator.CopyPropertiesFromView(itemSeperator);
+
+ return result;
+ }
+ }
+
+ static DefaultLinearItem() {}
+
+ /// <summary>
+ /// Creates a new instance of DefaultLinearItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultLinearItem() : base()
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultLinearItem with style.
+ /// </summary>
+ /// <param name="style">Create DefaultLinearItem by style defined in UX.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultLinearItem(string style) : base(style)
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultLinearItem with style.
+ /// </summary>
+ /// <param name="itemStyle">Create DefaultLinearItem by style customized by user.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultLinearItem(DefaultLinearItemStyle itemStyle) : base(itemStyle)
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Icon part of DefaultLinearItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public View Icon
+ {
+ get
+ {
+ if (itemIcon == null)
+ {
+ itemIcon = CreateIcon(ItemStyle?.Icon);
+ if (itemIcon != null)
+ {
+ layoutChanged = true;
+ Add(itemIcon);
+ itemIcon.Relayout += OnIconRelayout;
+ }
+ }
+ return itemIcon;
+ }
+ set
+ {
+ itemIcon = value;
+ }
+ }
+
+ /* open when imageView using Uri not string.
+ /// <summary>
+ /// Icon image's resource url. Only activatable for icon as ImageView.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string IconUrl
+ {
+ get
+ {
+ return (Icon as ImageView)?.ResourceUrl;
+ }
+ set
+ {
+ if (itemIcon != null && !(itemIcon is ImageView))
+ {
+ // Tizen.Log.Error("IconUrl only can set Icon is ImageView");
+ return;
+ }
+ (Icon as ImageView).ResourceUrl = value;
+ }
+ }
+ */
+
+ /// <summary>
+ /// DefaultLinearItem's text part of DefaultLinearItem
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TextLabel Label
+ {
+ get
+ {
+ if (itemLabel == null)
+ {
+ itemLabel = CreateLabel(ItemStyle?.Label);
+ if (itemLabel != null)
+ {
+ layoutChanged = true;
+ Add(itemLabel);
+ }
+ }
+ return itemLabel;
+ }
+ internal set
+ {
+ itemLabel = value;
+ AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Label, itemLabel.Text);
+ }
+ }
+
+ /// <summary>
+ /// The text of DefaultLinearItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Text
+ {
+ get
+ {
+ return Label.Text;
+ }
+ set
+ {
+ Label.Text = value;
+ }
+ }
+
+ /// <summary>
+ /// DefaultLinearItem's secondary text part of DefaultLinearItem
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TextLabel SubLabel
+ {
+ get
+ {
+ if (itemSubLabel == null)
+ {
+ itemSubLabel = CreateLabel(ItemStyle?.SubLabel);
+ if (itemLabel != null)
+ {
+ layoutChanged = true;
+ Add(itemSubLabel);
+ }
+ }
+ return itemSubLabel;
+ }
+ internal set
+ {
+ itemSubLabel = value;
+ AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Label, itemSubLabel.Text);
+ }
+ }
+
+ /// <summary>
+ /// The text of DefaultLinearItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string SubText
+ {
+ get
+ {
+ return SubLabel.Text;
+ }
+ set
+ {
+ SubLabel.Text = value;
+ }
+ }
+
+ /// <summary>
+ /// Extra icon part of DefaultLinearItem. it will place next of label.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public View Extra
+ {
+ get
+ {
+ if (itemExtra == null)
+ {
+ itemExtra = CreateIcon(ItemStyle?.Extra);
+ if (itemExtra != null)
+ {
+ layoutChanged = true;
+ Add(itemExtra);
+ itemExtra.Relayout += OnExtraRelayout;
+ }
+ }
+ return itemExtra;
+ }
+ set
+ {
+ if (itemExtra != null) Remove(itemExtra);
+ itemExtra = value;
+ Add(itemExtra);
+ }
+ }
+
+ /// <summary>
+ /// Seperator devider of DefaultLinearItem. it will place at the end of item.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public View Seperator
+ {
+ get
+ {
+ if (itemSeperator == null)
+ {
+ itemSeperator = new View(ItemStyle?.Seperator)
+ {
+ //need to consider horizontal/vertical!
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 2,
+ ExcludeLayouting = true
+ };
+ layoutChanged = true;
+ Add(itemSeperator);
+ }
+ return itemSeperator;
+ }
+ }
+
+ /// <summary>
+ /// Apply style to DefaultLinearItemStyle.
+ /// </summary>
+ /// <param name="viewStyle">The style to apply.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void ApplyStyle(ViewStyle viewStyle)
+ {
+
+ base.ApplyStyle(viewStyle);
+ if (viewStyle != null && viewStyle is DefaultLinearItemStyle defaultStyle)
+ {
+ if (itemLabel != null)
+ itemLabel.ApplyStyle(defaultStyle.Label);
+ if (itemSubLabel != null)
+ itemSubLabel.ApplyStyle(defaultStyle.SubLabel);
+ if (itemIcon != null)
+ itemIcon.ApplyStyle(defaultStyle.Icon);
+ if (itemExtra != null)
+ itemExtra.ApplyStyle(defaultStyle.Extra);
+ if (itemSeperator != null)
+ itemSeperator.ApplyStyle(defaultStyle.Seperator);
+ }
+ }
+
+ /// <inheritdoc />
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void OnRelayout(Vector2 size, RelayoutContainer container)
+ {
+ base.OnRelayout(size, container);
+
+ if (prevSize != Size)
+ {
+ prevSize = Size;
+ if (itemSeperator)
+ {
+ var margin = itemSeperator.Margin;
+ itemSeperator.SizeWidth = SizeWidth - margin.Start - margin.End;
+ itemSeperator.SizeHeight = itemSeperator.HeightSpecification;
+ itemSeperator.Position = new Position(margin.Start, SizeHeight - margin.Bottom -itemSeperator.SizeHeight);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates Item's text part.
+ /// </summary>
+ /// <return>The created Item's text part.</return>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual TextLabel CreateLabel(TextLabelStyle style)
+ {
+ return new TextLabel(style)
+ {
+ HorizontalAlignment = HorizontalAlignment.Begin,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ }
+
+ /// <summary>
+ /// Creates Item's icon part.
+ /// </summary>
+ /// <return>The created Item's icon part.</return>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual ImageView CreateIcon(ViewStyle style)
+ {
+ return new ImageView(style);
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void MeasureChild()
+ {
+ var pad = Padding;
+ if (itemLabel)
+ {
+ var margin = itemLabel.Margin;
+ itemLabel.SizeWidth = SizeWidth - pad.Start - pad.End - margin.Start - margin.End;
+ }
+
+ if (itemSubLabel)
+ {
+ var margin = itemSubLabel.Margin;
+ itemSubLabel.SizeWidth = SizeWidth - pad.Start - pad.End - margin.Start - margin.End;
+ }
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void LayoutChild()
+ {
+ if (!layoutChanged) return;
+ if (itemLabel == null) return;
+
+ layoutChanged = false;
+
+ if (itemIcon != null)
+ {
+ RelativeLayout.SetLeftTarget(itemIcon, this);
+ RelativeLayout.SetLeftRelativeOffset(itemIcon, 0.0F);
+ RelativeLayout.SetRightTarget(itemIcon, this);
+ RelativeLayout.SetRightRelativeOffset(itemIcon, 0.0F);
+ RelativeLayout.SetTopTarget(itemIcon, this);
+ RelativeLayout.SetTopRelativeOffset(itemIcon, 0.0F);
+ RelativeLayout.SetBottomTarget(itemIcon, this);
+ RelativeLayout.SetBottomRelativeOffset(itemIcon, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemIcon, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetHorizontalAlignment(itemIcon, RelativeLayout.Alignment.Start);
+ }
+
+ if (itemExtra != null)
+ {
+ RelativeLayout.SetLeftTarget(itemExtra, this);
+ RelativeLayout.SetLeftRelativeOffset(itemExtra, 1.0F);
+ RelativeLayout.SetRightTarget(itemExtra, this);
+ RelativeLayout.SetRightRelativeOffset(itemIcon, 1.0F);
+ RelativeLayout.SetTopTarget(itemExtra, this);
+ RelativeLayout.SetTopRelativeOffset(itemExtra, 0.0F);
+ RelativeLayout.SetBottomTarget(itemExtra, this);
+ RelativeLayout.SetBottomRelativeOffset(itemExtra, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemExtra, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetHorizontalAlignment(itemExtra, RelativeLayout.Alignment.End);
+ }
+
+ if (itemSubLabel != null)
+ {
+ if (itemIcon)
+ {
+ RelativeLayout.SetLeftTarget(itemSubLabel, itemIcon);
+ RelativeLayout.SetLeftRelativeOffset(itemSubLabel, 1.0F);
+ }
+ else
+ {
+ RelativeLayout.SetLeftTarget(itemSubLabel, this);
+ RelativeLayout.SetLeftRelativeOffset(itemSubLabel, 0.0F);
+ }
+ if (itemExtra)
+ {
+ RelativeLayout.SetRightTarget(itemSubLabel, itemExtra);
+ RelativeLayout.SetRightRelativeOffset(itemSubLabel, 0.0F);
+ }
+ else
+ {
+ RelativeLayout.SetRightTarget(itemSubLabel, this);
+ RelativeLayout.SetRightRelativeOffset(itemSubLabel, 1.0F);
+ }
+
+ RelativeLayout.SetTopTarget(itemSubLabel, this);
+ RelativeLayout.SetTopRelativeOffset(itemSubLabel, 1.0F);
+ RelativeLayout.SetBottomTarget(itemSubLabel, this);
+ RelativeLayout.SetBottomRelativeOffset(itemSubLabel, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemSubLabel, RelativeLayout.Alignment.End);
+
+ RelativeLayout.SetHorizontalAlignment(itemSubLabel, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetFillHorizontal(itemSubLabel, true);
+ }
+
+ if (itemIcon)
+ {
+ RelativeLayout.SetLeftTarget(itemLabel, itemIcon);
+ RelativeLayout.SetLeftRelativeOffset(itemLabel, 1.0F);
+ }
+ else
+ {
+ RelativeLayout.SetLeftTarget(itemLabel, this);
+ RelativeLayout.SetLeftRelativeOffset(itemLabel, 0.0F);
+ }
+ if (itemExtra)
+ {
+ RelativeLayout.SetRightTarget(itemLabel, itemExtra);
+ RelativeLayout.SetRightRelativeOffset(itemLabel, 0.0F);
+ }
+ else
+ {
+ RelativeLayout.SetRightTarget(itemLabel, this);
+ RelativeLayout.SetRightRelativeOffset(itemLabel, 1.0F);
+ }
+
+ RelativeLayout.SetTopTarget(itemLabel, this);
+ RelativeLayout.SetTopRelativeOffset(itemLabel, 0.0F);
+
+ if (itemSubLabel)
+ {
+ RelativeLayout.SetBottomTarget(itemLabel, itemSubLabel);
+ RelativeLayout.SetBottomRelativeOffset(itemLabel, 0.0F);
+ }
+ else
+ {
+ RelativeLayout.SetBottomTarget(itemLabel, this);
+ RelativeLayout.SetBottomRelativeOffset(itemLabel, 1.0F);
+ }
+ RelativeLayout.SetVerticalAlignment(itemLabel, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetHorizontalAlignment(itemLabel, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetFillHorizontal(itemLabel, true);
+
+ if (prevSize != Size)
+ {
+ prevSize = Size;
+ if (itemSeperator)
+ {
+ var margin = itemSeperator.Margin;
+ itemSeperator.SizeWidth = SizeWidth - margin.Start - margin.End;
+ itemSeperator.SizeHeight = itemSeperator.HeightSpecification;
+ itemSeperator.Position = new Position(margin.Start, SizeHeight - margin.Bottom -itemSeperator.SizeHeight);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Dispose Item and all children on it.
+ /// </summary>
+ /// <param name="type">Dispose type.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void Dispose(DisposeTypes type)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (type == DisposeTypes.Explicit)
+ {
+ //Extension : Extension?.OnDispose(this);
+
+ if (itemIcon != null)
+ {
+ Utility.Dispose(itemIcon);
+ }
+ if (itemExtra != null)
+ {
+ Utility.Dispose(itemExtra);
+ }
+ if (itemLabel != null)
+ {
+ Utility.Dispose(itemLabel);
+ }
+ if (itemSubLabel != null)
+ {
+ Utility.Dispose(itemSubLabel);
+ }
+ if (itemSeperator != null)
+ {
+ Utility.Dispose(itemSeperator);
+ }
+ }
+
+ base.Dispose(type);
+ }
+
+ /// <summary>
+ /// Get DefaultLinearItem style.
+ /// </summary>
+ /// <returns>The default DefaultLinearItem style.</returns>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override ViewStyle CreateViewStyle()
+ {
+ return new DefaultLinearItemStyle();
+ }
+
+ private void Initialize()
+ {
+ base.OnInitialize();
+ Layout = new RelativeLayout();
+ var seperator = Seperator;
+ layoutChanged = true;
+ LayoutDirectionChanged += OnLayoutDirectionChanged;
+ }
+
+ private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
+ {
+ MeasureChild();
+ LayoutChild();
+ }
+
+ private void OnIconRelayout(object sender, EventArgs e)
+ {
+ MeasureChild();
+ LayoutChild();
+ }
+
+ private void OnExtraRelayout(object sender, EventArgs e)
+ {
+ MeasureChild();
+ LayoutChild();
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// DefaultTitleItem is one kind of common component, a DefaultTitleItem clearly describes what action will occur when the user selects it.
+ /// DefaultTitleItem may contain text or an icon.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class DefaultTitleItem : RecyclerViewItem
+ {
+ private TextLabel itemLabel;
+ private View itemIcon;
+ private View itemSeperator;
+ private bool layoutChanged;
+ private Size prevSize;
+ private DefaultTitleItemStyle ItemStyle => ViewStyle as DefaultTitleItemStyle;
+
+ /// <summary>
+ /// Return a copied Style instance of DefaultTitleItem
+ /// </summary>
+ /// <remarks>
+ /// It returns copied Style instance and changing it does not effect to the DefaultTitleItem.
+ /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new DefaultTitleItemStyle Style
+ {
+ get
+ {
+ var result = new DefaultTitleItemStyle(ItemStyle);
+ result.CopyPropertiesFromView(this);
+ if (itemLabel) result.Label.CopyPropertiesFromView(itemLabel);
+ if (itemIcon) result.Icon.CopyPropertiesFromView(itemIcon);
+ if (itemSeperator) result.Seperator.CopyPropertiesFromView(itemSeperator);
+
+ return result;
+ }
+ }
+
+ static DefaultTitleItem() {}
+
+ /// <summary>
+ /// Creates a new instance of DefaultTitleItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultTitleItem() : base()
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultTitleItem with style.
+ /// </summary>
+ /// <param name="style">Create DefaultTitleItem by style defined in UX.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultTitleItem(string style) : base(style)
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultTitleItem with style.
+ /// </summary>
+ /// <param name="itemStyle">Create DefaultTitleItem by style customized by user.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultTitleItem(DefaultTitleItemStyle itemStyle) : base(itemStyle)
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Icon part of DefaultTitleItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public View Icon
+ {
+ get
+ {
+ if (itemIcon == null)
+ {
+ itemIcon = CreateIcon(ItemStyle?.Icon);
+ if (itemIcon != null)
+ {
+ layoutChanged = true;
+ Add(itemIcon);
+ itemIcon.Relayout += OnIconRelayout;
+ }
+ }
+ return itemIcon;
+ }
+ set
+ {
+ itemIcon = value;
+ }
+ }
+
+ /* open when imageView using Uri not string.
+ /// <summary>
+ /// Icon image's resource url. Only activatable for icon as ImageView.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string IconUrl
+ {
+ get
+ {
+ return (Icon as ImageView)?.ResourceUrl;
+ }
+ set
+ {
+ if (itemIcon != null && !(itemIcon is ImageView))
+ {
+ // Tizen.Log.Error("IconUrl only can set Icon is ImageView");
+ return;
+ }
+ (Icon as ImageView).ResourceUrl = value;
+ }
+ }
+ */
+
+ /// <summary>
+ /// DefaultTitleItem's text part of DefaultTitleItem
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TextLabel Label
+ {
+ get
+ {
+ if (itemLabel == null)
+ {
+ itemLabel = CreateLabel(ItemStyle?.Label);
+ if (itemLabel != null)
+ {
+ layoutChanged = true;
+ Add(itemLabel);
+ }
+ }
+ return itemLabel;
+ }
+ internal set
+ {
+ itemLabel = value;
+ AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Label, itemLabel.Text);
+ }
+ }
+
+ /// <summary>
+ /// The text of DefaultTitleItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string Text
+ {
+ get
+ {
+ return Label.Text;
+ }
+ set
+ {
+ Label.Text = value;
+ }
+ }
+
+ /// <summary>
+ /// Seperator devider of DefaultTitleItem. it will place at the end of item.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public View Seperator
+ {
+ get
+ {
+ if (itemSeperator == null)
+ {
+ itemSeperator = new View(ItemStyle?.Seperator)
+ {
+ //need to consider horizontal/vertical!
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 2,
+ ExcludeLayouting = true
+ };
+ layoutChanged = true;
+ Add(itemSeperator);
+ }
+ return itemSeperator;
+ }
+ }
+
+ /// <summary>
+ /// Apply style to DefaultTitleItemStyle.
+ /// </summary>
+ /// <param name="viewStyle">The style to apply.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void ApplyStyle(ViewStyle viewStyle)
+ {
+
+ base.ApplyStyle(viewStyle);
+ if (viewStyle != null && viewStyle is DefaultTitleItemStyle defaultStyle)
+ {
+ if (itemLabel != null)
+ itemLabel.ApplyStyle(defaultStyle.Label);
+ if (itemIcon != null)
+ itemIcon.ApplyStyle(defaultStyle.Icon);
+ if (itemSeperator != null)
+ itemSeperator.ApplyStyle(defaultStyle.Seperator);
+ }
+ }
+
+ /// <inheritdoc />
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void OnRelayout(Vector2 size, RelayoutContainer container)
+ {
+ base.OnRelayout(size, container);
+
+ if (prevSize != Size)
+ {
+ prevSize = Size;
+ if (itemSeperator)
+ {
+ var margin = itemSeperator.Margin;
+ itemSeperator.SizeWidth = SizeWidth - margin.Start - margin.End;
+ itemSeperator.SizeHeight = itemSeperator.HeightSpecification;
+ itemSeperator.Position = new Position(margin.Start, SizeHeight - margin.Bottom -itemSeperator.SizeHeight);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates Item's text part.
+ /// </summary>
+ /// <return>The created Item's text part.</return>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual TextLabel CreateLabel(TextLabelStyle style)
+ {
+ return new TextLabel(style)
+ {
+ HorizontalAlignment = HorizontalAlignment.Begin,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ }
+
+ /// <summary>
+ /// Creates Item's icon part.
+ /// </summary>
+ /// <return>The created Item's icon part.</return>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual ImageView CreateIcon(ViewStyle style)
+ {
+ return new ImageView(style);
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void MeasureChild()
+ {
+ if (itemLabel)
+ {
+ var pad = Padding;
+ var margin = itemLabel.Margin;
+ itemLabel.SizeWidth = SizeWidth - pad.Start - pad.End - margin.Start - margin.End;
+ }
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void LayoutChild()
+ {
+ if (!layoutChanged) return;
+ if (itemLabel == null) return;
+
+ layoutChanged = false;
+
+ if (itemIcon != null)
+ {
+ RelativeLayout.SetLeftTarget(itemIcon, this);
+ RelativeLayout.SetLeftRelativeOffset(itemIcon, 1.0F);
+ RelativeLayout.SetRightTarget(itemIcon, this);
+ RelativeLayout.SetRightRelativeOffset(itemIcon, 1.0F);
+ RelativeLayout.SetTopTarget(itemIcon, this);
+ RelativeLayout.SetTopRelativeOffset(itemIcon, 0.0F);
+ RelativeLayout.SetBottomTarget(itemIcon, this);
+ RelativeLayout.SetBottomRelativeOffset(itemIcon, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemIcon, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetHorizontalAlignment(itemIcon, RelativeLayout.Alignment.End);
+ }
+
+ RelativeLayout.SetLeftTarget(itemLabel, this);
+ RelativeLayout.SetLeftRelativeOffset(itemLabel, 0.0F);
+ if (itemIcon)
+ {
+ RelativeLayout.SetRightTarget(itemLabel, itemIcon);
+ RelativeLayout.SetRightRelativeOffset(itemLabel, 0.0F);
+ }
+ else
+ {
+ RelativeLayout.SetRightTarget(itemLabel, this);
+ RelativeLayout.SetRightRelativeOffset(itemLabel, 1.0F);
+ }
+
+ RelativeLayout.SetTopTarget(itemLabel, this);
+ RelativeLayout.SetTopRelativeOffset(itemLabel, 0.0F);
+ RelativeLayout.SetBottomTarget(itemLabel, this);
+ RelativeLayout.SetBottomRelativeOffset(itemLabel, 1.0F);
+ RelativeLayout.SetVerticalAlignment(itemLabel, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetHorizontalAlignment(itemLabel, RelativeLayout.Alignment.Center);
+ RelativeLayout.SetFillHorizontal(itemLabel, true);
+
+ if (prevSize != Size)
+ {
+ prevSize = Size;
+ if (itemSeperator)
+ {
+ var margin = itemSeperator.Margin;
+ itemSeperator.SizeWidth = SizeWidth - margin.Start - margin.End;
+ itemSeperator.SizeHeight = itemSeperator.HeightSpecification;
+ itemSeperator.Position = new Position(margin.Start, SizeHeight - margin.Bottom -itemSeperator.SizeHeight);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Dispose Item and all children on it.
+ /// </summary>
+ /// <param name="type">Dispose type.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void Dispose(DisposeTypes type)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (type == DisposeTypes.Explicit)
+ {
+ //Extension : Extension?.OnDispose(this);
+
+ if (itemIcon != null)
+ {
+ Utility.Dispose(itemIcon);
+ }
+ if (itemLabel != null)
+ {
+ Utility.Dispose(itemLabel);
+ }
+ if (itemSeperator != null)
+ {
+ Utility.Dispose(itemSeperator);
+ }
+ }
+
+ base.Dispose(type);
+ }
+
+ /// <summary>
+ /// Get DefaultTitleItem style.
+ /// </summary>
+ /// <returns>The default DefaultTitleItem style.</returns>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override ViewStyle CreateViewStyle()
+ {
+ return new DefaultTitleItemStyle();
+ }
+
+ private void Initialize()
+ {
+ base.OnInitialize();
+ Layout = new RelativeLayout();
+ var seperator = Seperator;
+ layoutChanged = true;
+ LayoutDirectionChanged += OnLayoutDirectionChanged;
+ }
+
+ private void OnLayoutDirectionChanged(object sender, LayoutDirectionChangedEventArgs e)
+ {
+ MeasureChild();
+ LayoutChild();
+ }
+
+ private void OnIconRelayout(object sender, EventArgs e)
+ {
+ MeasureChild();
+ LayoutChild();
+ }
+
+ private void OnExtraRelayout(object sender, EventArgs e)
+ {
+ MeasureChild();
+ LayoutChild();
+ }
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility; // To use AccessibilityManager
+
+namespace Tizen.NUI.Components
+{
+ public partial class RecyclerViewItem
+ {
+ internal RecyclerView ParentItemsView = null;
+ internal object ParentGroup = null;
+ internal bool isGroupHeader;
+ internal bool isGroupFooter;
+ private bool styleApplied = false;
+
+ /// <summary>
+ /// Update ViewItem State.
+ /// </summary>
+ internal void UpdateState()
+ {
+ if (!styleApplied) return;
+
+ ControlState sourceState = ControlState;
+ ControlState targetState;
+
+ // Normal, Disabled
+ targetState = IsEnabled ? ControlState.Normal : ControlState.Disabled;
+
+ // Selected, DisabledSelected
+ if (IsSelected) targetState += ControlState.Selected;
+
+ // Pressed, PressedSelected
+ if (IsPressed) targetState += ControlState.Pressed;
+
+ // Focused, FocusedPressed, FocusedPressedSelected, DisabledFocused, DisabledSelectedFocused
+ if (IsFocused) targetState += ControlState.Focused;
+
+ if (sourceState != targetState)
+ {
+ ControlState = targetState;
+ OnUpdate();
+ }
+ }
+
+ internal override bool OnAccessibilityActivated()
+ {
+ if (!IsEnabled)
+ {
+ return false;
+ }
+
+ // Touch Down
+ IsPressed = true;
+ UpdateState();
+
+ // Touch Up
+ bool clicked = IsPressed && IsEnabled;
+ IsPressed = false;
+
+ if (IsSelectable)
+ {
+ //IsSelected = !IsSelected;
+ }
+ else
+ {
+ UpdateState();
+ }
+
+ if (clicked)
+ {
+ ClickedEventArgs eventArgs = new ClickedEventArgs();
+ OnClickedInternal(eventArgs);
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Called when the ViewItem is Clicked by a user
+ /// </summary>
+ /// <param name="eventArgs">The click information.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void OnClicked(ClickedEventArgs eventArgs)
+ {
+ //Console.WriteLine("On Clicked Called {0}", this.Index);
+ }
+
+ /// <summary>
+ /// Called when the ViewItem need to be updated
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void OnUpdate()
+ {
+ base.OnUpdate();
+ UpdateContent();
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override bool HandleControlStateOnTouch(Touch touch)
+ {
+ if (!IsEnabled || null == touch)
+ {
+ return false;
+ }
+
+ PointStateType state = touch.GetState(0);
+
+ switch (state)
+ {
+ case PointStateType.Down:
+ IsPressed = true;
+ UpdateState();
+ return true;
+ case PointStateType.Interrupted:
+ IsPressed = false;
+ UpdateState();
+ return true;
+ case PointStateType.Up:
+ {
+ bool clicked = IsPressed && IsEnabled;
+ IsPressed = false;
+
+ if (!clicked) return true;
+
+ if (IsSelectable)
+ {
+ if (ParentItemsView as CollectionView)
+ {
+ CollectionView colView = ParentItemsView as CollectionView;
+ switch (colView.SelectionMode)
+ {
+ case ItemSelectionMode.SingleSelection :
+ colView.SelectedItem = IsSelected ? null : BindingContext;
+ break;
+ case ItemSelectionMode.MultipleSelections :
+ var selectedItems = colView.SelectedItems;
+ if (selectedItems.Contains(BindingContext)) selectedItems.Remove(BindingContext);
+ else selectedItems.Add(BindingContext);
+ break;
+ case ItemSelectionMode.None :
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Extension : Extension?.SetTouchInfo(touch);
+ UpdateState();
+ }
+
+ if (clicked)
+ {
+ ClickedEventArgs eventArgs = new ClickedEventArgs();
+ OnClickedInternal(eventArgs);
+ }
+
+ return true;
+ }
+ default:
+ break;
+ }
+ return base.HandleControlStateOnTouch(touch);
+ }
+
+
+ /// <summary>
+ /// Measure child, it can be override.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void MeasureChild()
+ {
+ }
+
+ /// <summary>
+ /// Layout child, it can be override.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void LayoutChild()
+ {
+ }
+
+ /// <summary>
+ /// Dispose Item and all children on it.
+ /// </summary>
+ /// <param name="type">Dispose type.</param>
+ protected override void Dispose(DisposeTypes type)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ if (type == DisposeTypes.Explicit)
+ {
+ //
+ }
+
+ base.Dispose(type);
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void OnControlStateChanged(ControlStateChangedEventArgs controlStateChangedInfo)
+ {
+ if (controlStateChangedInfo == null) throw new ArgumentNullException(nameof(controlStateChangedInfo));
+ base.OnControlStateChanged(controlStateChangedInfo);
+
+ var stateEnabled = !controlStateChangedInfo.CurrentState.Contains(ControlState.Disabled);
+
+ if (IsEnabled != stateEnabled)
+ {
+ IsEnabled = stateEnabled;
+ }
+
+ var statePressed = controlStateChangedInfo.CurrentState.Contains(ControlState.Pressed);
+
+ if (IsPressed != statePressed)
+ {
+ IsPressed = statePressed;
+ }
+ }
+
+ /// <summary>
+ /// Get ViewItem style.
+ /// </summary>
+ /// <returns>The default ViewItem style.</returns>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override ViewStyle CreateViewStyle()
+ {
+ return new RecyclerViewItemStyle();
+ }
+
+ /// <summary>
+ /// It is hijack by using protected, style copy problem when class inherited from Button.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ private void Initialize()
+ {
+ //FIXME!
+ IsCreateByXaml = true;
+ Layout = new AbsoluteLayout();
+ UpdateState();
+
+ AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ViewItem");
+ }
+
+ /// <summary>
+ /// Update the Content. it can be override.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void UpdateContent()
+ {
+ MeasureChild();
+ LayoutChild();
+
+ Sensitive = IsEnabled;
+ }
+
+
+ /// FIXME!! This has to be done in Element or View class.
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void OnBindingContextChanged()
+ {
+ foreach (View child in Children)
+ {
+ SetChildInheritedBindingContext(child, BindingContext);
+ }
+ }
+
+ private void OnClickedInternal(ClickedEventArgs eventArgs)
+ {
+ Command?.Execute(CommandParameter);
+ OnClicked(eventArgs);
+
+ Clicked?.Invoke(this, eventArgs);
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+using Tizen.NUI.Accessibility;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// This class provides a basic item for CollectionView.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public partial class RecyclerViewItem : Control
+ {
+ /// <summary>
+ /// Property of boolean Enable flag.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool), typeof(RecyclerViewItem), true, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var instance = (RecyclerViewItem)bindable;
+ if (newValue != null)
+ {
+ bool newEnabled = (bool)newValue;
+ if (instance.isEnabled != newEnabled)
+ {
+ instance.isEnabled = newEnabled;
+ if (instance.ItemStyle != null)
+ {
+ instance.ItemStyle.IsEnabled = newEnabled;
+ }
+ instance.UpdateState();
+ }
+ }
+ },
+ defaultValueCreator: (bindable) => ((RecyclerViewItem)bindable).isEnabled);
+
+ /// <summary>
+ /// Property of boolean Selected flag.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool), typeof(RecyclerViewItem), true, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var instance = (RecyclerViewItem)bindable;
+ if (newValue != null)
+ {
+ bool newSelected = (bool)newValue;
+ if (instance.isSelected != newSelected)
+ {
+ instance.isSelected = newSelected;
+
+ if (instance.ItemStyle != null)
+ {
+ instance.ItemStyle.IsSelected = newSelected;
+ }
+
+ if (instance.isSelectable)
+ {
+ instance.UpdateState();
+ }
+ }
+ }
+ },
+ defaultValueCreator: (bindable) =>
+ {
+ var instance = (RecyclerViewItem)bindable;
+ return instance.isSelectable && instance.isSelected;
+ });
+
+ /// <summary>
+ /// Property of boolean Selectable flag.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty IsSelectableProperty = BindableProperty.Create(nameof(IsSelectable), typeof(bool), typeof(RecyclerViewItem), true, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var instance = (RecyclerViewItem)bindable;
+ if (newValue != null)
+ {
+ bool newSelectable = (bool)newValue;
+ if (instance.isSelectable != newSelectable)
+ {
+ instance.isSelectable = newSelectable;
+
+ if (instance.ItemStyle != null)
+ {
+ instance.ItemStyle.IsSelectable = newSelectable;
+ }
+
+ instance.UpdateState();
+ }
+ }
+ },
+ defaultValueCreator: (bindable) => ((RecyclerViewItem)bindable).isSelectable);
+
+ private bool isSelected = false;
+ private bool isSelectable = true;
+ private bool isEnabled = true;
+ private RecyclerViewItemStyle ItemStyle => ViewStyle as RecyclerViewItemStyle;
+
+ /// <summary>
+ /// Return a copied Style instance of Toast
+ /// </summary>
+ /// <remarks>
+ /// It returns copied Style instance and changing it does not effect to the Toast.
+ /// Style setting is possible by using constructor or the function of ApplyStyle(ViewStyle viewStyle)
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new RecyclerViewItemStyle Style
+ {
+ get
+ {
+ var result = new RecyclerViewItemStyle(ItemStyle);
+ result.CopyPropertiesFromView(this);
+ return result;
+ }
+ }
+
+ static RecyclerViewItem() {}
+
+ /// <summary>
+ /// Creates a new instance of RecyclerViewItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecyclerViewItem() : base()
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Creates a new instance of RecyclerViewItem with style.
+ /// </summary>
+ /// <param name="style">Create RecyclerViewItem by special style defined in UX.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecyclerViewItem(string style) : base(style)
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// Creates a new instance of a RecyclerViewItem with style.
+ /// </summary>
+ /// <param name="itemStyle">Create RecyclerViewItem by style customized by user.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecyclerViewItem(RecyclerViewItemStyle itemStyle) : base(itemStyle)
+ {
+ Initialize();
+ }
+
+ /// <summary>
+ /// An event for the RecyclerViewItem clicked signal which can be used to subscribe or unsubscribe the event handler provided by the user.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler<ClickedEventArgs> Clicked;
+
+ /// <summary>
+ /// Flag to decide RecyclerViewItem can be selected or not.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsSelectable
+ {
+ get => (bool)GetValue(IsSelectableProperty);
+ set => SetValue(IsSelectableProperty, value);
+ }
+
+ /// <summary>
+ /// Flag to decide selected state in RecyclerViewItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsSelected
+ {
+ get => (bool)GetValue(IsSelectedProperty);
+ set => SetValue(IsSelectedProperty, value);
+ }
+
+ /// <summary>
+ /// Flag to decide enable or disable in RecyclerViewItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEnabled
+ {
+ get => (bool)GetValue(IsEnabledProperty);
+ set => SetValue(IsEnabledProperty, value);
+ }
+
+ /// <summary>
+ /// Data index which is binded to item.
+ /// Can access to data using this index.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int Index { get; internal set; } = 0;
+
+ /// <summary>
+ /// DataTemplate of this view object
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DataTemplate Template { get; internal set; }
+
+ /// <summary>
+ /// State of Realization
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsRealized { get; internal set; }
+ internal bool IsHeader { get; set; }
+ internal bool IsFooter { get; set; }
+ internal bool IsPressed { get; set; } = false;
+
+ /// <summary>
+ /// Called after a key event is received by the view that has had its focus set.
+ /// </summary>
+ /// <param name="key">The key event.</param>
+ /// <returns>True if the key event should be consumed.</returns>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool OnKey(Key key)
+ {
+ if (!IsEnabled || null == key)
+ {
+ return false;
+ }
+
+ if (key.State == Key.StateType.Down)
+ {
+ if (key.KeyPressedName == "Return")
+ {
+ IsPressed = true;
+ UpdateState();
+ }
+ }
+ else if (key.State == Key.StateType.Up)
+ {
+ if (key.KeyPressedName == "Return")
+ {
+ bool clicked = IsPressed && IsEnabled;
+
+ IsPressed = false;
+
+ if (IsSelectable)
+ {
+ // Extension : Extension?.SetTouchInfo(touch);
+ if (ParentItemsView as CollectionView)
+ {
+ CollectionView colView = ParentItemsView as CollectionView;
+ switch (colView.SelectionMode)
+ {
+ case ItemSelectionMode.SingleSelection :
+ colView.SelectedItem = IsSelected ? null : BindingContext;
+ break;
+ case ItemSelectionMode.MultipleSelections :
+ var selectedItems = colView.SelectedItems;
+ if (selectedItems.Contains(BindingContext)) selectedItems.Remove(BindingContext);
+ else selectedItems.Add(BindingContext);
+ break;
+ case ItemSelectionMode.None :
+ break;
+ }
+ }
+ }
+ else
+ {
+ UpdateState();
+ }
+
+ if (clicked)
+ {
+ ClickedEventArgs eventArgs = new ClickedEventArgs();
+ OnClickedInternal(eventArgs);
+ }
+ }
+ }
+ return base.OnKey(key);
+ }
+
+ /// <summary>
+ /// Called when the control gain key input focus. Should be overridden by derived classes if they need to customize what happens when the focus is gained.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void OnFocusGained()
+ {
+ base.OnFocusGained();
+ UpdateState();
+ }
+
+ /// <summary>
+ /// Called when the control loses key input focus.
+ /// Should be overridden by derived classes if they need to customize
+ /// what happens when the focus is lost.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void OnFocusLost()
+ {
+ base.OnFocusLost();
+ UpdateState();
+ }
+
+ /// <summary>
+ /// Apply style to RecyclerViewItem.
+ /// </summary>
+ /// <param name="viewStyle">The style to apply.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void ApplyStyle(ViewStyle viewStyle)
+ {
+ styleApplied = false;
+
+ base.ApplyStyle(viewStyle);
+ if (viewStyle != null)
+ {
+ //Extension = RecyclerViewItemStyle.CreateExtension();
+ }
+
+ styleApplied = true;
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// Selection mode of CollecitonView.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum ItemSelectionMode
+ {
+ /// <summary>
+ /// None of item can be selected.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ None,
+ /// <summary>
+ /// Single selection. select item exculsively so previous selected item will be unselected.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ SingleSelection,
+ /// <summary>
+ /// Multiple selections. select multiple items and previous selected item still remains selected.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ MultipleSelections
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// Size calculation strategy for CollectionView.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum ItemSizingStrategy
+ {
+
+ /// <summary>
+ /// Measure all items in advanced.
+ /// Estimate first item size for all, and when scroll reached position,
+ /// measure strictly. Note : This will make scroll bar trembling.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ MeasureAll,
+ /// <summary>
+ /// Measure first item and deligate size for all items.
+ /// if template is selector, the size of first item from each template will be deligated.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ MeasureFirst,
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+
+namespace Tizen.NUI.Components
+{
+ internal sealed class EmptySource : IItemSource
+ {
+ public int Count => 0;
+
+ public bool HasHeader { get; set; }
+ public bool HasFooter { get; set; }
+
+ public void Dispose()
+ {
+
+ }
+
+ public bool IsHeader(int index)
+ {
+ return HasHeader && index == 0;
+ }
+
+ public bool IsFooter(int index)
+ {
+ if (!HasFooter)
+ {
+ return false;
+ }
+
+ if (HasHeader)
+ {
+ return index == 1;
+ }
+
+ return index == 0;
+ }
+
+ public int GetPosition(object item)
+ {
+ return -1;
+ }
+
+ public object GetItem(int position)
+ {
+ throw new IndexOutOfRangeException("IItemSource is empty");
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// Base interface for encapsulated data source in RecyclerView.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IItemSource : IDisposable
+ {
+ /// <summary>
+ /// Count of data source.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ int Count { get; }
+
+ /// <summary>
+ /// Position integer value of data object.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ int GetPosition(object item);
+
+ /// <summary>
+ /// Item object in position.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ object GetItem(int position);
+
+ /// <summary>
+ /// Flag of header existence.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ bool HasHeader { get; set; }
+
+ /// <summary>
+ /// Flag of Footer existence.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ bool HasFooter { get; set; }
+
+ /// <summary>
+ /// Boolean checker for position is header or not.
+ /// 0 index will be header if header exist.
+ /// warning: if header exist, all item index will be increased.
+ /// </summary>
+ /// <param name="position">The position for checking header.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ bool IsHeader(int position);
+
+ /// <summary>
+ /// Boolean checker for position is footer or not.
+ /// last index will be footer if footer exist.
+ /// warning: footer will be place original data count or data count + 1.
+ /// </summary>
+ /// <param name="position">The position for checking footer.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ bool IsFooter(int position);
+ }
+
+ /// <summary>
+ /// Base interface for encapsulated data source with group structure in CollectionView.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IGroupableItemSource : IItemSource
+ {
+ /// <summary>
+ /// Boolean checker for position is group header or not
+ /// </summary>
+ /// <param name="position">The position for checking group header.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ bool IsGroupHeader(int position);
+
+ /// <summary>
+ /// Boolean checker for position is group footer or not
+ /// </summary>
+ /// <param name="position">The position for checking group footer.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ bool IsGroupFooter(int position);
+
+ /// <summary>
+ /// Boolean checker for position is group footer or not
+ /// </summary>
+ /// <param name="position">The position for checking group footer.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ object GetGroupParent(int position);
+
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+//using AndroidX.RecyclerView.Widget; ??? need to find whot it needs? adapter?
+
+namespace Tizen.NUI.Components
+{
+ internal static class ItemsSourceFactory
+ {
+ public static IItemSource Create(IEnumerable itemsSource, ICollectionChangedNotifier notifier)
+ {
+ if (itemsSource == null)
+ {
+ return new EmptySource();
+ }
+
+ switch (itemsSource)
+ {
+ case IList list when itemsSource is INotifyCollectionChanged:
+ return new ObservableItemSource(new MarshalingObservableCollection(list), notifier);
+ case IEnumerable _ when itemsSource is INotifyCollectionChanged:
+ return new ObservableItemSource(itemsSource, notifier);
+ case IEnumerable<object> generic:
+ return new ListSource(generic);
+ }
+
+ return new ListSource(itemsSource);
+ }
+
+ public static IItemSource Create(RecyclerView recyclerView)
+ {
+ return Create(recyclerView.ItemsSource, recyclerView);
+ }
+
+ public static IGroupableItemSource Create(CollectionView colView)
+ {
+ var source = colView.ItemsSource;
+
+ if (colView.IsGrouped && source != null)
+ return new ObservableGroupedSource(colView, colView);
+
+ else
+ return new UngroupedItemSource(Create(colView.ItemsSource, colView));
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Components
+{
+ sealed class ListSource : IItemSource, IList
+ {
+ IList _itemsSource;
+
+ public ListSource()
+ {
+ }
+
+ public ListSource(IEnumerable<object> enumerable)
+ {
+ _itemsSource = new List<object>(enumerable);
+ }
+
+ public ListSource(IEnumerable enumerable)
+ {
+ _itemsSource = new List<object>();
+
+ if (enumerable == null)
+ return;
+
+ foreach (object item in enumerable)
+ {
+ _itemsSource.Add(item);
+ }
+ }
+
+ public int Count => _itemsSource.Count + (HasHeader ? 1 : 0) + (HasFooter ? 1 : 0);
+
+ public bool HasHeader { get; set; }
+ public bool HasFooter { get; set; }
+
+ public bool IsReadOnly => _itemsSource.IsReadOnly;
+
+ public bool IsFixedSize => _itemsSource.IsFixedSize;
+
+ public object SyncRoot => _itemsSource.SyncRoot;
+
+ public bool IsSynchronized => _itemsSource.IsSynchronized;
+
+ object IList.this[int index] { get => _itemsSource[index]; set => _itemsSource[index] = value; }
+
+ public void Dispose()
+ {
+
+ }
+
+ public bool IsFooter(int index)
+ {
+ return HasFooter && index == Count - 1;
+ }
+
+ public bool IsHeader(int index)
+ {
+ return HasHeader && index == 0;
+ }
+
+ public int GetPosition(object item)
+ {
+ for (int n = 0; n < _itemsSource.Count; n++)
+ {
+ var elementByIndex = _itemsSource[n];
+ var isEqual = elementByIndex == item || (elementByIndex != null && item != null && elementByIndex.Equals(item));
+
+ if (isEqual)
+ {
+ return AdjustPosition(n);
+ }
+ }
+
+ return -1;
+ }
+
+ public object GetItem(int position)
+ {
+ return _itemsSource[AdjustIndexRequest(position)];
+ }
+
+ int AdjustIndexRequest(int index)
+ {
+ return index - (HasHeader ? 1 : 0);
+ }
+
+ int AdjustPosition(int index)
+ {
+ return index + (HasHeader ? 1 : 0);
+ }
+ public int Add(object value)
+ {
+ return _itemsSource.Add(value);
+ }
+
+ public bool Contains(object value)
+ {
+ return _itemsSource.Contains(value);
+ }
+
+ public void Clear()
+ {
+ _itemsSource.Clear();
+ }
+
+ public int IndexOf(object value)
+ {
+ return _itemsSource.IndexOf(value);
+ }
+
+ public void Insert(int index, object value)
+ {
+ _itemsSource.Insert(index, value);
+ }
+
+ public void Remove(object value)
+ {
+ _itemsSource.Remove(value);
+ }
+
+ public void RemoveAt(int index)
+ {
+ _itemsSource.RemoveAt(index);
+ }
+
+ public void CopyTo(Array array, int index)
+ {
+ _itemsSource.CopyTo(array, index);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _itemsSource.GetEnumerator();
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace Tizen.NUI.Components
+{
+ // Wraps a List which implements INotifyCollectionChanged (usually an ObservableCollection)
+ // and marshals all of the list modifications to the main thread. Modifications to the underlying
+ // collection which are made off of the main thread remain invisible to consumers on the main thread
+ // until they have been processed by the main thread.
+
+ internal class MarshalingObservableCollection : List<object>, INotifyCollectionChanged
+ {
+ readonly IList internalCollection;
+
+ public MarshalingObservableCollection(IList list)
+ {
+ if (!(list is INotifyCollectionChanged incc))
+ {
+ throw new ArgumentException($"{nameof(list)} must implement {nameof(INotifyCollectionChanged)}");
+ }
+
+ internalCollection = list;
+ incc.CollectionChanged += InternalCollectionChanged;
+
+ foreach (var item in internalCollection)
+ {
+ Add(item);
+ }
+ }
+
+ class ResetNotifyCollectionChangedEventArgs : NotifyCollectionChangedEventArgs
+ {
+ public IList Items { get; }
+ public ResetNotifyCollectionChangedEventArgs(IList items)
+ : base(NotifyCollectionChangedAction.Reset) => Items = items;
+ }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
+ {
+ CollectionChanged?.Invoke(this, args);
+ }
+
+ void InternalCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ if (args.Action == NotifyCollectionChangedAction.Reset)
+ {
+ var items = new List<object>();
+ for (int n = 0; n < internalCollection.Count; n++)
+ {
+ items.Add(internalCollection[n]);
+ }
+
+ args = new ResetNotifyCollectionChangedEventArgs(items);
+ }
+/*
+ if (Device.IsInvokeRequired)
+ {
+ Device.BeginInvokeOnMainThread(() => HandleCollectionChange(args));
+ }
+ else
+ {
+ HandleCollectionChange(args);
+ }
+*/
+
+ HandleCollectionChange(args);
+ }
+
+ void HandleCollectionChange(NotifyCollectionChangedEventArgs args)
+ {
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ Add(args);
+ break;
+ case NotifyCollectionChangedAction.Move:
+ Move(args);
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ Remove(args);
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ Replace(args);
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ Reset(args);
+ break;
+ }
+ }
+
+ void Move(NotifyCollectionChangedEventArgs args)
+ {
+ var count = args.OldItems.Count;
+
+ for (int n = 0; n < count; n++)
+ {
+ var toMove = this[args.OldStartingIndex];
+ RemoveAt(args.OldStartingIndex);
+ Insert(args.NewStartingIndex, toMove);
+ }
+
+ OnCollectionChanged(args);
+ }
+
+ void Remove(NotifyCollectionChangedEventArgs args)
+ {
+ var startIndex = args.OldStartingIndex + args.OldItems.Count - 1;
+ for (int n = startIndex; n >= args.OldStartingIndex; n--)
+ {
+ RemoveAt(n);
+ }
+
+ OnCollectionChanged(args);
+ }
+
+ void Replace(NotifyCollectionChangedEventArgs args)
+ {
+ var startIndex = args.NewStartingIndex;
+ foreach (var item in args.NewItems)
+ {
+ this[startIndex] = item;
+ startIndex += 1;
+ }
+
+ OnCollectionChanged(args);
+ }
+
+ void Add(NotifyCollectionChangedEventArgs args)
+ {
+ var startIndex = args.NewStartingIndex;
+ foreach (var item in args.NewItems)
+ {
+ Insert(startIndex, item);
+ startIndex += 1;
+ }
+
+ OnCollectionChanged(args);
+ }
+
+ void Reset(NotifyCollectionChangedEventArgs args)
+ {
+ if (!(args is ResetNotifyCollectionChangedEventArgs resetArgs))
+ {
+ throw new InvalidOperationException($"Cannot guarantee collection accuracy for Resets which do not use {nameof(ResetNotifyCollectionChangedEventArgs)}");
+ }
+
+ Clear();
+ foreach (var item in resetArgs.Items)
+ {
+ Add(item);
+ }
+
+ OnCollectionChanged(args);
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace Tizen.NUI.Components
+{
+ internal class ObservableGroupedSource : IGroupableItemSource, ICollectionChangedNotifier
+ {
+ readonly ICollectionChangedNotifier notifier;
+ readonly IList groupSource;
+ readonly List<IItemSource> groups = new List<IItemSource>();
+ readonly bool hasGroupHeaders;
+ readonly bool hasGroupFooters;
+ bool disposed;
+
+ public int Count
+ {
+ get
+ {
+ var groupContents = 0;
+
+ for (int n = 0; n < groups.Count; n++)
+ {
+ groupContents += groups[n].Count;
+ }
+
+ return (HasHeader ? 1 : 0)
+ + (HasFooter ? 1 : 0)
+ + groupContents;
+ }
+ }
+
+ public bool HasHeader { get; set; }
+ public bool HasFooter { get; set; }
+
+ public ObservableGroupedSource(CollectionView colView, ICollectionChangedNotifier changedNotifier)
+ {
+ var source = colView.ItemsSource;
+
+ notifier = changedNotifier;
+ groupSource = source as IList ?? new ListSource(source);
+
+ hasGroupFooters = colView.GroupFooterTemplate != null;
+ hasGroupHeaders = colView.GroupHeaderTemplate != null;
+ HasHeader = colView.Header != null;
+ HasFooter = colView.Footer != null;
+
+ if (groupSource is INotifyCollectionChanged incc)
+ {
+ incc.CollectionChanged += CollectionChanged;
+ }
+
+ UpdateGroupTracking();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public bool IsFooter(int position)
+ {
+ if (!HasFooter)
+ {
+ return false;
+ }
+
+ return position == Count - 1;
+ }
+
+ public bool IsHeader(int position)
+ {
+ return HasHeader && position == 0;
+ }
+
+ public bool IsGroupHeader(int position)
+ {
+ if (IsFooter(position) || IsHeader(position))
+ {
+ return false;
+ }
+
+ var (group, inGroup) = GetGroupAndIndex(position);
+
+ return groups[group].IsHeader(inGroup);
+ }
+
+ public bool IsGroupFooter(int position)
+ {
+ if (IsFooter(position) || IsHeader(position))
+ {
+ return false;
+ }
+
+ var (group, inGroup) = GetGroupAndIndex(position);
+
+ return groups[group].IsFooter(inGroup);
+ }
+
+ public int GetPosition(object item)
+ {
+ int previousGroupsOffset = 0;
+
+ for (int groupIndex = 0; groupIndex < groupSource.Count; groupIndex++)
+ {
+ if (groupSource[groupIndex].Equals(item))
+ {
+ return AdjustPositionForHeader(groupIndex);
+ }
+
+ var group = groups[groupIndex];
+ var inGroup = group.GetPosition(item);
+
+ if (inGroup > -1)
+ {
+ return AdjustPositionForHeader(previousGroupsOffset + inGroup);
+ }
+
+ previousGroupsOffset += group.Count;
+ }
+
+ return -1;
+ }
+
+ public object GetItem(int position)
+ {
+ var (group, inGroup) = GetGroupAndIndex(position);
+
+ if (IsGroupFooter(position) || IsGroupHeader(position))
+ {
+ // This is looping to find the group/index twice, need to make it less inefficient
+ return groupSource[group];
+ }
+
+ return groups[group].GetItem(inGroup);
+ }
+
+ public object GetGroupParent(int position)
+ {
+ var (group, inGroup) = GetGroupAndIndex(position);
+ return groupSource[group];
+ }
+
+ // The ICollectionChangedNotifier methods are called by child observable items sources (i.e., the groups)
+ // This class can then translate their local changes into global positions for upstream notification
+ // (e.g., to the actual RecyclerView.Adapter, so that it can notify the RecyclerView and handle animating
+ // the changes)
+ public void NotifyDataSetChanged()
+ {
+ Reload();
+ }
+
+ public void NotifyItemChanged(IItemSource group, int localIndex)
+ {
+ localIndex = GetAbsolutePosition(group, localIndex);
+ notifier.NotifyItemChanged(this, localIndex);
+ }
+
+ public void NotifyItemInserted(IItemSource group, int localIndex)
+ {
+ localIndex = GetAbsolutePosition(group, localIndex);
+ notifier.NotifyItemInserted(this, localIndex);
+ }
+
+ public void NotifyItemMoved(IItemSource group, int localFromIndex, int localToIndex)
+ {
+ localFromIndex = GetAbsolutePosition(group, localFromIndex);
+ localToIndex = GetAbsolutePosition(group, localToIndex);
+ notifier.NotifyItemMoved(this, localFromIndex, localToIndex);
+ }
+
+ public void NotifyItemRangeChanged(IItemSource group, int localStartIndex, int localEndIndex)
+ {
+ localStartIndex = GetAbsolutePosition(group, localStartIndex);
+ localEndIndex = GetAbsolutePosition(group, localEndIndex);
+ notifier.NotifyItemRangeChanged(this, localStartIndex, localEndIndex);
+ }
+
+ public void NotifyItemRangeInserted(IItemSource group, int localIndex, int count)
+ {
+ localIndex = GetAbsolutePosition(group, localIndex);
+ notifier.NotifyItemRangeInserted(this, localIndex, count);
+ }
+
+ public void NotifyItemRangeRemoved(IItemSource group, int localIndex, int count)
+ {
+ localIndex = GetAbsolutePosition(group, localIndex);
+ notifier.NotifyItemRangeRemoved(this, localIndex, count);
+ }
+
+ public void NotifyItemRemoved(IItemSource group, int localIndex)
+ {
+ localIndex = GetAbsolutePosition(group, localIndex);
+ notifier.NotifyItemRemoved(this, localIndex);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ disposed = true;
+
+ if (disposing)
+ {
+ ClearGroupTracking();
+
+ if (groupSource is INotifyCollectionChanged notifyCollectionChanged)
+ {
+ notifyCollectionChanged.CollectionChanged -= CollectionChanged;
+ }
+ if (groupSource is IDisposable dispoableSource) dispoableSource.Dispose();
+ }
+ }
+
+ void UpdateGroupTracking()
+ {
+ ClearGroupTracking();
+
+ for (int n = 0; n < groupSource.Count; n++)
+ {
+ var source = ItemsSourceFactory.Create(groupSource[n] as IEnumerable, this);
+ source.HasFooter = hasGroupFooters;
+ source.HasHeader = hasGroupHeaders;
+ groups.Add(source);
+ }
+ }
+
+ void ClearGroupTracking()
+ {
+ for (int n = groups.Count - 1; n >= 0; n--)
+ {
+ groups[n].Dispose();
+ groups.RemoveAt(n);
+ }
+ }
+
+ void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {/*
+ if (Device.IsInvokeRequired)
+ {
+ Device.BeginInvokeOnMainThread(() => CollectionChanged(args));
+ }
+ else
+ {
+ */
+ CollectionChanged(args);
+ //}
+ }
+
+ void CollectionChanged(NotifyCollectionChangedEventArgs args)
+ {
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ Add(args);
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ Remove(args);
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ Replace(args);
+ break;
+ case NotifyCollectionChangedAction.Move:
+ Move(args);
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ Reload();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(args));
+ }
+ }
+
+ void Reload()
+ {
+ UpdateGroupTracking();
+ notifier.NotifyDataSetChanged();
+ }
+
+ void Add(NotifyCollectionChangedEventArgs args)
+ {
+ var groupIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : groupSource.IndexOf(args.NewItems[0]);
+ var groupCount = args.NewItems.Count;
+
+ UpdateGroupTracking();
+
+ // Determine the absolute starting position and the number of items in the groups being added
+ var absolutePosition = GetAbsolutePosition(groups[groupIndex], 0);
+ var itemCount = CountItemsInGroups(groupIndex, groupCount);
+
+ if (itemCount == 1)
+ {
+ notifier.NotifyItemInserted(this, absolutePosition);
+ return;
+ }
+
+ notifier.NotifyItemRangeInserted(this, absolutePosition, itemCount);
+ }
+
+ void Remove(NotifyCollectionChangedEventArgs args)
+ {
+ var groupIndex = args.OldStartingIndex;
+
+ if (groupIndex < 0)
+ {
+ // INCC implementation isn't giving us enough information to know where the removed groups was in the
+ // collection. So the best we can do is a full reload.
+ Reload();
+ return;
+ }
+
+ // If we have a start index, we can be more clever about removing the group(s) (and get the nifty animations)
+ var groupCount = args.OldItems.Count;
+
+ var absolutePosition = GetAbsolutePosition(groups[groupIndex], 0);
+
+ // Figure out how many items are in the groups we're removing
+ var itemCount = CountItemsInGroups(groupIndex, groupCount);
+
+ if (itemCount == 1)
+ {
+ notifier.NotifyItemRemoved(this, absolutePosition);
+
+ UpdateGroupTracking();
+
+ return;
+ }
+
+ notifier.NotifyItemRangeRemoved(this, absolutePosition, itemCount);
+
+ UpdateGroupTracking();
+ }
+
+ void Replace(NotifyCollectionChangedEventArgs args)
+ {
+ var groupCount = args.NewItems.Count;
+
+ if (groupCount != args.OldItems.Count)
+ {
+ // The original and replacement sets are of unequal size; this means that most everything currently in
+ // view will have to be updated. So just reload the whole thing.
+ Reload();
+ return;
+ }
+
+ var newStartIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : groupSource.IndexOf(args.NewItems[0]);
+ var oldStartIndex = args.OldStartingIndex > -1 ? args.OldStartingIndex : groupSource.IndexOf(args.OldItems[0]);
+
+ var newItemCount = CountItemsInGroups(newStartIndex, groupCount);
+ var oldItemCount = CountItemsInGroups(oldStartIndex, groupCount);
+
+ if (newItemCount != oldItemCount)
+ {
+ // The original and replacement sets are of unequal size; this means that most everything currently in
+ // view will have to be updated. So just reload the whole thing.
+ Reload();
+ return;
+ }
+
+ // We are replacing one set of items with a set of equal size; we can do a simple item or range notification
+ var firstGroupIndex = Math.Min(newStartIndex, oldStartIndex);
+ var absolutePosition = GetAbsolutePosition(groups[firstGroupIndex], 0);
+
+ if (newItemCount == 1)
+ {
+ notifier.NotifyItemChanged(this, absolutePosition);
+ UpdateGroupTracking();
+ }
+ else
+ {
+ notifier.NotifyItemRangeChanged(this, absolutePosition, newItemCount * 2);
+ UpdateGroupTracking();
+ }
+ }
+
+ void Move(NotifyCollectionChangedEventArgs args)
+ {
+ var start = Math.Min(args.OldStartingIndex, args.NewStartingIndex);
+ var end = Math.Max(args.OldStartingIndex, args.NewStartingIndex) + args.NewItems.Count;
+
+ var itemCount = CountItemsInGroups(start, end - start);
+ var absolutePosition = GetAbsolutePosition(groups[start], 0);
+
+ notifier.NotifyItemRangeChanged(this, absolutePosition, itemCount);
+
+ UpdateGroupTracking();
+ }
+
+ int GetAbsolutePosition(IItemSource group, int indexInGroup)
+ {
+ var groupIndex = groups.IndexOf(group);
+
+ var runningIndex = 0;
+
+ for (int n = 0; n < groupIndex; n++)
+ {
+ runningIndex += groups[n].Count;
+ }
+
+ return AdjustPositionForHeader(runningIndex + indexInGroup);
+ }
+
+ (int, int) GetGroupAndIndex(int absolutePosition)
+ {
+ absolutePosition = AdjustIndexForHeader(absolutePosition);
+
+ var group = 0;
+ var localIndex = 0;
+
+ while (absolutePosition > 0)
+ {
+ localIndex += 1;
+
+ if (localIndex == groups[group].Count)
+ {
+ group += 1;
+ localIndex = 0;
+ }
+
+ absolutePosition -= 1;
+ }
+
+ return (group, localIndex);
+ }
+
+ int AdjustIndexForHeader(int index)
+ {
+ return index - (HasHeader ? 1 : 0);
+ }
+
+ int AdjustPositionForHeader(int position)
+ {
+ return position + (HasHeader ? 1 : 0);
+ }
+
+ int CountItemsInGroups(int groupStartIndex, int groupCount)
+ {
+ var itemCount = 0;
+ for (int n = 0; n < groupCount; n++)
+ {
+ itemCount += groups[groupStartIndex + n].Count;
+ }
+ return itemCount;
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+
+namespace Tizen.NUI.Components
+{
+ internal class ObservableItemSource : IItemSource
+ {
+ readonly IEnumerable itemsSource;
+ readonly ICollectionChangedNotifier notifier;
+ bool disposed;
+
+ public ObservableItemSource(IEnumerable itemSource, ICollectionChangedNotifier changedNotifier)
+ {
+ itemsSource = itemSource as IList ?? itemSource as IEnumerable;
+ notifier = changedNotifier;
+
+ ((INotifyCollectionChanged)itemSource).CollectionChanged += CollectionChanged;
+ }
+
+
+ internal event NotifyCollectionChangedEventHandler CollectionItemsSourceChanged;
+
+ public int Count => ItemsCount() + (HasHeader ? 1 : 0) + (HasFooter ? 1 : 0);
+
+ public bool HasHeader { get; set; }
+ public bool HasFooter { get; set; }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public bool IsFooter(int index)
+ {
+ return HasFooter && index == Count - 1;
+ }
+
+ public bool IsHeader(int index)
+ {
+ return HasHeader && index == 0;
+ }
+
+ public int GetPosition(object item)
+ {
+ for (int n = 0; n < ItemsCount(); n++)
+ {
+ var elementByIndex = ElementAt(n);
+ var isEqual = elementByIndex == item || (elementByIndex != null && item != null && elementByIndex.Equals(item));
+
+ if (isEqual)
+ {
+ return AdjustPositionForHeader(n);
+ }
+ }
+
+ return -1;
+ }
+
+ public object GetItem(int position)
+ {
+ return ElementAt(AdjustIndexForHeader(position));
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ disposed = true;
+
+ if (disposing)
+ {
+ ((INotifyCollectionChanged)itemsSource).CollectionChanged -= CollectionChanged;
+ }
+ }
+
+ int AdjustIndexForHeader(int index)
+ {
+ return index - (HasHeader ? 1 : 0);
+ }
+
+ int AdjustPositionForHeader(int position)
+ {
+ return position + (HasHeader ? 1 : 0);
+ }
+
+ void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {/*
+ if (Device.IsInvokeRequired)
+ {
+ Device.BeginInvokeOnMainThread(() => CollectionChanged(args));
+ }
+ else
+ {*/
+ CollectionChanged(args);
+ //}
+
+ }
+
+ void CollectionChanged(NotifyCollectionChangedEventArgs args)
+ {
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ Add(args);
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ Remove(args);
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ Replace(args);
+ break;
+ case NotifyCollectionChangedAction.Move:
+ Move(args);
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ notifier.NotifyDataSetChanged();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(args));
+ }
+ CollectionItemsSourceChanged?.Invoke(this, args);
+ }
+
+ void Move(NotifyCollectionChangedEventArgs args)
+ {
+ var count = args.NewItems.Count;
+
+ if (count == 1)
+ {
+ // For a single item, we can use NotifyItemMoved and get the animation
+ notifier.NotifyItemMoved(this, AdjustPositionForHeader(args.OldStartingIndex), AdjustPositionForHeader(args.NewStartingIndex));
+ return;
+ }
+
+ var start = AdjustPositionForHeader(Math.Min(args.OldStartingIndex, args.NewStartingIndex));
+ var end = AdjustPositionForHeader(Math.Max(args.OldStartingIndex, args.NewStartingIndex) + count);
+ notifier.NotifyItemRangeChanged(this, start, end);
+ }
+
+ void Add(NotifyCollectionChangedEventArgs args)
+ {
+ var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
+ startIndex = AdjustPositionForHeader(startIndex);
+ var count = args.NewItems.Count;
+
+ if (count == 1)
+ {
+ notifier.NotifyItemInserted(this, startIndex);
+ return;
+ }
+
+ notifier.NotifyItemRangeInserted(this, startIndex, count);
+ }
+
+ void Remove(NotifyCollectionChangedEventArgs args)
+ {
+ var startIndex = args.OldStartingIndex;
+
+ if (startIndex < 0)
+ {
+ // INCC implementation isn't giving us enough information to know where the removed items were in the
+ // collection. So the best we can do is a NotifyDataSetChanged()
+ notifier.NotifyDataSetChanged();
+ return;
+ }
+
+ startIndex = AdjustPositionForHeader(startIndex);
+
+ // If we have a start index, we can be more clever about removing the item(s) (and get the nifty animations)
+ var count = args.OldItems.Count;
+
+ if (count == 1)
+ {
+ notifier.NotifyItemRemoved(this, startIndex);
+ return;
+ }
+
+ notifier.NotifyItemRangeRemoved(this, startIndex, count);
+ }
+
+ void Replace(NotifyCollectionChangedEventArgs args)
+ {
+ var startIndex = args.NewStartingIndex > -1 ? args.NewStartingIndex : IndexOf(args.NewItems[0]);
+ startIndex = AdjustPositionForHeader(startIndex);
+ var newCount = args.NewItems.Count;
+
+ if (newCount == args.OldItems.Count)
+ {
+ // We are replacing one set of items with a set of equal size; we can do a simple item or range
+ // notification to the adapter
+ if (newCount == 1)
+ {
+ notifier.NotifyItemChanged(this, startIndex);
+ }
+ else
+ {
+ notifier.NotifyItemRangeChanged(this, startIndex, newCount);
+ }
+
+ return;
+ }
+
+ // The original and replacement sets are of unequal size; this means that everything currently in view will
+ // have to be updated. So we just have to use NotifyDataSetChanged and let the RecyclerView update everything
+ notifier.NotifyDataSetChanged();
+ }
+
+ internal int ItemsCount()
+ {
+ if (itemsSource is IList list)
+ return list.Count;
+
+ int count = 0;
+ foreach (var item in itemsSource)
+ count++;
+ return count;
+ }
+
+ internal object ElementAt(int index)
+ {
+ if (itemsSource is IList list)
+ return list[index];
+
+ int count = 0;
+ foreach (var item in itemsSource)
+ {
+ if (count == index)
+ return item;
+ count++;
+ }
+
+ return -1;
+ }
+
+ internal int IndexOf(object item)
+ {
+ if (itemsSource is IList list)
+ return list.IndexOf(item);
+
+ int count = 0;
+ foreach (var i in itemsSource)
+ {
+ if (i == item)
+ return count;
+ count++;
+ }
+
+ return -1;
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace Tizen.NUI.Components
+{
+ internal class UngroupedItemSource : IGroupableItemSource
+ {
+ readonly IItemSource source;
+
+ public UngroupedItemSource(IItemSource itemSource)
+ {
+ source = itemSource;
+ }
+
+ public int Count => source.Count;
+
+ public bool HasHeader { get => source.HasHeader; set => source.HasHeader = value; }
+ public bool HasFooter { get => source.HasFooter; set => source.HasFooter = value; }
+
+ public void Dispose()
+ {
+ source.Dispose();
+ }
+
+ public object GetItem(int position)
+ {
+ return source.GetItem(position);
+ }
+
+ public int GetPosition(object item)
+ {
+ return source.GetPosition(item);
+ }
+
+ public bool IsFooter(int position)
+ {
+ return source.IsFooter(position);
+ }
+
+ public bool IsGroupFooter(int position)
+ {
+ return false;
+ }
+
+ public bool IsGroupHeader(int position)
+ {
+ return false;
+ }
+
+ public bool IsHeader(int position)
+ {
+ return source.IsHeader(position);
+ }
+
+ public object GetGroupParent(int position)
+ {
+ return null;
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// This class implements a grid box layout.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class GridLayouter : ItemsLayouter
+ {
+ private CollectionView colView;
+ private Size2D sizeCandidate;
+ private int spanSize = 1;
+ private float align = 0.5f;
+ private bool hasHeader;
+ private float headerSize;
+ private bool hasFooter;
+ private float footerSize;
+ private bool isGrouped;
+ private readonly List<GroupInfo> groups = new List<GroupInfo>();
+ private float groupHeaderSize;
+ private float groupFooterSize;
+ private GroupInfo Visited;
+
+ /// <summary>
+ /// Clean up ItemsLayouter.
+ /// </summary>
+ /// <param name="view"> ItemsView of layouter. </param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Initialize(RecyclerView view)
+ {
+ colView = view as CollectionView;
+ if (colView == null)
+ {
+ throw new ArgumentException("GridLayouter only can be applied CollectionView.", nameof(view));
+ }
+
+ // 1. Clean Up
+ foreach (RecyclerViewItem item in VisibleItems)
+ {
+ colView.UnrealizeItem(item, false);
+ }
+ VisibleItems.Clear();
+ groups.Clear();
+
+ FirstVisible = 0;
+ LastVisible = 0;
+
+ IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
+
+ RecyclerViewItem header = colView?.Header;
+ RecyclerViewItem footer = colView?.Footer;
+ float width, height;
+ int count = colView.InternalItemSource.Count;
+ int pureCount = count - (header ? 1 : 0) - (footer ? 1 : 0);
+
+ // 2. Get the header / footer and size deligated item and measure the size.
+ if (header != null)
+ {
+ MeasureChild(colView, header);
+
+ width = header.Layout != null ? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
+ height = header.Layout != null ? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
+
+ headerSize = IsHorizontal ? width : height;
+ hasHeader = true;
+
+ colView.UnrealizeItem(header);
+ }
+
+ if (footer != null)
+ {
+ MeasureChild(colView, footer);
+
+ width = footer.Layout != null ? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
+ height = footer.Layout != null ? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
+
+ footerSize = IsHorizontal ? width : height;
+ footer.Index = count - 1;
+ hasFooter = true;
+
+ colView.UnrealizeItem(footer);
+ }
+
+ int firstIndex = header ? 1 : 0;
+
+ if (colView.IsGrouped)
+ {
+ isGrouped = true;
+
+ if (colView.GroupHeaderTemplate != null)
+ {
+ while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
+ //must be always true
+ if (colView.InternalItemSource.IsGroupHeader(firstIndex))
+ {
+ RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
+ firstIndex++;
+
+ if (groupHeader == null) throw new Exception("[" + firstIndex + "] Group Header failed to realize!");
+
+ // Need to Set proper hieght or width on scroll direciton.
+ if (groupHeader.Layout == null)
+ {
+ width = groupHeader.WidthSpecification;
+ height = groupHeader.HeightSpecification;
+ }
+ else
+ {
+ MeasureChild(colView, groupHeader);
+
+ width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
+ height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
+ }
+ //Console.WriteLine("[NUI] GroupHeader Size {0} :{0}", width, height);
+ // pick the StepCandidate.
+ groupHeaderSize = IsHorizontal ? width : height;
+ colView.UnrealizeItem(groupHeader);
+ }
+ }
+ else
+ {
+ groupHeaderSize = 0F;
+ }
+
+ if (colView.GroupFooterTemplate != null)
+ {
+ int firstFooter = firstIndex;
+ while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
+ //must be always true
+ if (colView.InternalItemSource.IsGroupFooter(firstFooter))
+ {
+ RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
+
+ if (groupFooter == null) throw new Exception("[" + firstFooter + "] Group Footer failed to realize!");
+ // Need to Set proper hieght or width on scroll direciton.
+ if (groupFooter.Layout == null)
+ {
+ width = groupFooter.WidthSpecification;
+ height = groupFooter.HeightSpecification;
+ }
+ else
+ {
+ MeasureChild(colView, groupFooter);
+
+ width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
+ height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
+ }
+ // pick the StepCandidate.
+ groupFooterSize = IsHorizontal ? width : height;
+
+ colView.UnrealizeItem(groupFooter);
+ }
+ }
+ else
+ {
+ groupFooterSize = 0F;
+ }
+ }
+ else isGrouped = false;
+
+ bool failed = false;
+ //Final Check of FirstIndex
+ while (colView.InternalItemSource.IsHeader(firstIndex) ||
+ colView.InternalItemSource.IsGroupHeader(firstIndex) ||
+ colView.InternalItemSource.IsGroupFooter(firstIndex))
+ {
+ if (colView.InternalItemSource.IsFooter(firstIndex))
+ {
+ StepCandidate = 0F;
+ failed = true;
+ break;
+ }
+ firstIndex++;
+ }
+
+ sizeCandidate = new Size2D(0, 0);
+ if (!failed)
+ {
+ // Get Size Deligate. FIXME if group exist index must be changed.
+ RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
+ if (sizeDeligate == null)
+ {
+ throw new Exception("Cannot create content from DatTemplate.");
+ }
+ sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
+
+ // Need to Set proper hieght or width on scroll direciton.
+ if (sizeDeligate.Layout == null)
+ {
+ width = sizeDeligate.WidthSpecification;
+ height = sizeDeligate.HeightSpecification;
+ }
+ else
+ {
+ MeasureChild(colView, sizeDeligate);
+
+ width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
+ height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
+ }
+ //Console.WriteLine("[NUI] item Size {0} :{1}", width, height);
+
+ // pick the StepCandidate.
+ StepCandidate = IsHorizontal ? width : height;
+ spanSize = IsHorizontal ? Convert.ToInt32(Math.Truncate((double)(colView.Size.Height / height))) :
+ Convert.ToInt32(Math.Truncate((double)(colView.Size.Width / width)));
+
+ sizeCandidate = new Size2D(Convert.ToInt32(width), Convert.ToInt32(height));
+
+ colView.UnrealizeItem(sizeDeligate);
+ }
+
+ if (StepCandidate < 1) StepCandidate = 1;
+ if (spanSize < 1) spanSize = 1;
+
+ if (isGrouped)
+ {
+ float Current = 0.0F;
+ IGroupableItemSource source = colView.InternalItemSource;
+ GroupInfo currentGroup = null;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (i == 0 && hasHeader)
+ {
+ Current += headerSize;
+ }
+ else if (i == count - 1 && hasFooter)
+ {
+ Current += footerSize;
+ }
+ else
+ {
+ //GroupHeader must always exist in group usage.
+ if (source.IsGroupHeader(i))
+ {
+ currentGroup = new GroupInfo()
+ {
+ GroupParent = source.GetGroupParent(i),
+ StartIndex = i,
+ Count = 1,
+ GroupSize = groupHeaderSize,
+ GroupPosition = Current
+ };
+ groups.Add(currentGroup);
+ Current += groupHeaderSize;
+ }
+ //optional
+ else if (source.IsGroupFooter(i))
+ {
+ //currentGroup.hasFooter = true;
+ currentGroup.Count++;
+ currentGroup.GroupSize += groupFooterSize;
+ Current += groupFooterSize;
+ }
+ else
+ {
+ currentGroup.Count++;
+ int index = i - currentGroup.StartIndex - 1; // groupHeader must always exist.
+ if ((index % spanSize) == 0)
+ {
+ currentGroup.GroupSize += StepCandidate;
+ Current += StepCandidate;
+ }
+ }
+ }
+ }
+ ScrollContentSize = Current;
+ }
+ else
+ {
+ // 3. Measure the scroller content size.
+ ScrollContentSize = StepCandidate * Convert.ToInt32(Math.Ceiling((double)pureCount / (double)spanSize));
+ if (hasHeader) ScrollContentSize += headerSize;
+ if (hasFooter) ScrollContentSize += footerSize;
+ }
+
+ if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+ else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+ base.Initialize(colView);
+ //Console.WriteLine("Init Done, StepCnadidate{0}, spanSize{1}, Scroll{2}", StepCandidate, spanSize, ScrollContentSize);
+ }
+
+ /// <summary>
+ /// This is called to find out where items are lain out according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <param name="force">boolean force flag to layouting forcely.</param>
+ public override void RequestLayout(float scrollPosition, bool force = false)
+ {
+ // Layouting is only possible after once it intialized.
+ if (!IsInitialized) return;
+ int LastIndex = colView.InternalItemSource.Count;
+
+ if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
+ PrevScrollPosition = Math.Abs(scrollPosition);
+
+ int prevFirstVisible = FirstVisible;
+ int prevLastVisible = LastVisible;
+ bool IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
+
+ (float X, float Y) visibleArea = (PrevScrollPosition,
+ PrevScrollPosition + (IsHorizontal ? colView.Size.Width : colView.Size.Height)
+ );
+
+ //Console.WriteLine("[NUI] itemsView [{0},{1}] [{2},{3}]", colView.Size.Width, colView.Size.Height, colView.ContentContainer.Size.Width, colView.ContentContainer.Size.Height);
+
+ // 1. Set First/Last Visible Item Index.
+ (int start, int end) = FindVisibleItems(visibleArea);
+ FirstVisible = start;
+ LastVisible = end;
+
+ //Console.WriteLine("[NUI] {0} :visibleArea before [{1},{2}] after [{3},{4}]", scrollPosition, prevFirstVisible, prevLastVisible, FirstVisible, LastVisible);
+
+ // 2. Unrealize invisible items.
+ List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
+ foreach (RecyclerViewItem item in VisibleItems)
+ {
+ if (item.Index < FirstVisible || item.Index > LastVisible)
+ {
+ //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
+ unrealizedItems.Add(item);
+ colView.UnrealizeItem(item);
+ }
+ }
+ VisibleItems.RemoveAll(unrealizedItems.Contains);
+
+ //Console.WriteLine("Realize Begin [{0} to {1}]", FirstVisible, LastVisible);
+ // 3. Realize and placing visible items.
+ for (int i = FirstVisible; i <= LastVisible; i++)
+ {
+ //Console.WriteLine("[NUI] Realize!");
+ RecyclerViewItem item = null;
+ // 4. Get item if visible or realize new.
+ if (i >= prevFirstVisible && i <= prevLastVisible)
+ {
+ item = GetVisibleItem(i);
+ if (item) continue;
+ }
+ if (item == null) item = colView.RealizeItem(i);
+ VisibleItems.Add(item);
+
+ (float x, float y) = GetItemPosition(i);
+ // 5. Placing item.
+ item.Position = new Position(x, y);
+ //Console.WriteLine("[NUI] ["+item.Index+"] ["+item.Position.X+", "+item.Position.Y+" ==== \n");
+ }
+ //Console.WriteLine("Realize Done");
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override (float X, float Y) GetItemPosition(object item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ if (colView == null) return (0, 0);
+
+ return GetItemPosition(colView.InternalItemSource.GetPosition(item));
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override (float X, float Y) GetItemSize(object item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ if (sizeCandidate == null) return (0, 0);
+
+ if (isGrouped)
+ {
+ int index = colView.InternalItemSource.GetPosition(item);
+ float view = (IsHorizontal ? colView.Size.Height : colView.Size.Width);
+
+ if (colView.InternalItemSource.IsGroupHeader(index))
+ {
+ return (IsHorizontal ? (groupHeaderSize, view) : (view, groupHeaderSize));
+ }
+ else if (colView.InternalItemSource.IsGroupFooter(index))
+ {
+ return (IsHorizontal ? (groupFooterSize, view) : (view, groupFooterSize));
+ }
+ }
+
+ return (sizeCandidate.Width, sizeCandidate.Height);
+ }
+
+ /// <inheritdoc/>
+ public override void NotifyItemSizeChanged(RecyclerViewItem item)
+ {
+ // All Item size need to be same in grid!
+ // if you want to change item size, change dataTemplate to re-initing.
+ return;
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override float CalculateLayoutOrientationSize()
+ {
+ //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
+ return ScrollContentSize;
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override float CalculateCandidateScrollPosition(float scrollPosition)
+ {
+ //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
+ return scrollPosition;
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ {
+ if (currentFocusedView == null)
+ throw new ArgumentNullException(nameof(currentFocusedView));
+
+ View nextFocusedView = null;
+ int targetSibling = -1;
+ bool IsHorizontal = colView.ScrollingDirection == ScrollableBase.Direction.Horizontal;
+
+ switch (direction)
+ {
+ case View.FocusDirection.Left:
+ {
+ targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
+ break;
+ }
+ case View.FocusDirection.Right:
+ {
+ targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
+ break;
+ }
+ case View.FocusDirection.Up:
+ {
+ targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
+ break;
+ }
+ case View.FocusDirection.Down:
+ {
+ targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
+ break;
+ }
+ }
+
+ if (targetSibling > -1 && targetSibling < Container.Children.Count)
+ {
+ RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
+ if (candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
+ {
+ nextFocusedView = candidate;
+ }
+ }
+ return nextFocusedView;
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
+ {
+ int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter ? 1 : 0);
+ int adds = spanSize * 2;
+ int skipGroup = -1;
+ (int start, int end) found = (0, 0);
+
+ // Header is Showing
+ if (hasHeader && visibleArea.X < headerSize)
+ {
+ found.start = 0;
+ }
+ else
+ {
+ if (isGrouped)
+ {
+ bool failed = true;
+ foreach (GroupInfo gInfo in groups)
+ {
+ skipGroup++;
+ // in the Group
+ if (gInfo.GroupPosition <= visibleArea.X &&
+ gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
+ {
+ if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.X)
+ {
+ found.start = gInfo.StartIndex - adds;
+ failed = false;
+ }
+ //can be step in spanSize...
+ for (int i = 1; i < gInfo.Count; i++)
+ {
+ if (!failed) break;
+ // Reach last index of group.
+ if (i == (gInfo.Count - 1))
+ {
+ found.start = gInfo.StartIndex + i - adds;
+ failed = false;
+ break;
+
+ }
+ else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.X - gInfo.GroupPosition - groupHeaderSize)
+ {
+ found.start = gInfo.StartIndex + i - adds;
+ failed = false;
+ break;
+ }
+ }
+ }
+ }
+ //footer only shows?
+ if (failed)
+ {
+ found.start = MaxIndex;
+ }
+ }
+ else
+ {
+ float visibleAreaX = visibleArea.X - (hasHeader ? headerSize : 0);
+ found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / StepCandidate)) - 1) * spanSize;
+ if (hasHeader) found.start += 1;
+ }
+ if (found.start < 0) found.start = 0;
+ }
+
+ if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
+ {
+ found.end = MaxIndex + 1;
+ }
+ else
+ {
+ if (isGrouped)
+ {
+ bool failed = true;
+ // can it be start from founded group...?
+ //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
+ foreach (GroupInfo gInfo in groups)
+ {
+ // in the Group
+ if (gInfo.GroupPosition <= visibleArea.Y &&
+ gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
+ {
+ if (gInfo.GroupPosition + groupHeaderSize >= visibleArea.Y)
+ {
+ found.end = gInfo.StartIndex + adds;
+ failed = false;
+ }
+ //can be step in spanSize...
+ for (int i = 1; i < gInfo.Count; i++)
+ {
+ if (!failed) break;
+ // Reach last index of group.
+ if (i == (gInfo.Count - 1))
+ {
+ found.end = gInfo.StartIndex + i + adds;
+ failed = false;
+ break;
+ }
+ else if ((((i - 1) / spanSize) * StepCandidate) + StepCandidate >= visibleArea.Y - gInfo.GroupPosition - groupHeaderSize)
+ {
+ found.end = gInfo.StartIndex + i + adds;
+ failed = false;
+ break;
+ }
+ }
+ }
+ }
+ //footer only shows?
+ if (failed)
+ {
+ found.start = MaxIndex;
+ }
+ }
+ else
+ {
+ float visibleAreaY = visibleArea.Y - (hasHeader ? headerSize : 0);
+ //Need to Consider GroupHeight!!!!
+ found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / StepCandidate)) + 1) * spanSize + adds;
+ if (hasHeader) found.end += 1;
+ }
+ if (found.end > (MaxIndex)) found.end = MaxIndex;
+ }
+ return found;
+ }
+
+ private (float X, float Y) GetItemPosition(int index)
+ {
+ float xPos, yPos;
+ if (sizeCandidate == null) return (0, 0);
+
+ if (hasHeader && index == 0)
+ {
+ return (0, 0);
+ }
+ if (hasFooter && index == colView.InternalItemSource.Count - 1)
+ {
+ xPos = IsHorizontal ? ScrollContentSize - footerSize : 0;
+ yPos = IsHorizontal ? 0 : ScrollContentSize - footerSize;
+ return (xPos, yPos);
+ }
+ if (isGrouped)
+ {
+ GroupInfo myGroup = GetGroupInfo(index);
+ if (colView.InternalItemSource.IsGroupHeader(index))
+ {
+ xPos = IsHorizontal ? myGroup.GroupPosition : 0;
+ yPos = IsHorizontal ? 0 : myGroup.GroupPosition;
+ }
+ else if (colView.InternalItemSource.IsGroupFooter(index))
+ {
+ xPos = IsHorizontal ? myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize : 0;
+ yPos = IsHorizontal ? 0 : myGroup.GroupPosition + myGroup.GroupSize - groupFooterSize;
+ }
+ else
+ {
+ int pureIndex = index - myGroup.StartIndex - 1;
+ int division = pureIndex / spanSize;
+ int remainder = pureIndex % spanSize;
+ int emptyArea = IsHorizontal ? (int)(colView.Size.Height - (sizeCandidate.Height * spanSize)) :
+ (int)(colView.Size.Width - (sizeCandidate.Width * spanSize));
+ if (division < 0) division = 0;
+ if (remainder < 0) remainder = 0;
+
+ xPos = IsHorizontal ? division * sizeCandidate.Width + myGroup.GroupPosition + groupHeaderSize : emptyArea * align + remainder * sizeCandidate.Width;
+ yPos = IsHorizontal ? emptyArea * align + remainder * sizeCandidate.Height : division * sizeCandidate.Height + myGroup.GroupPosition + groupHeaderSize;
+ }
+ }
+ else
+ {
+ int pureIndex = index - (colView.Header ? 1 : 0);
+ // int convert must be truncate value.
+ int division = pureIndex / spanSize;
+ int remainder = pureIndex % spanSize;
+ int emptyArea = IsHorizontal ? (int)(colView.Size.Height - (sizeCandidate.Height * spanSize)) :
+ (int)(colView.Size.Width - (sizeCandidate.Width * spanSize));
+ if (division < 0) division = 0;
+ if (remainder < 0) remainder = 0;
+
+ xPos = IsHorizontal ? division * sizeCandidate.Width + (hasHeader ? headerSize : 0) : emptyArea * align + remainder * sizeCandidate.Width;
+ yPos = IsHorizontal ? emptyArea * align + remainder * sizeCandidate.Height : division * sizeCandidate.Height + (hasHeader ? headerSize : 0);
+ }
+
+ return (xPos, yPos);
+ }
+
+ private RecyclerViewItem GetVisibleItem(int index)
+ {
+ foreach (RecyclerViewItem item in VisibleItems)
+ {
+ if (item.Index == index) return item;
+ }
+
+ return null;
+ }
+
+ private GroupInfo GetGroupInfo(int index)
+ {
+ if (Visited != null)
+ {
+ if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
+ return Visited;
+ }
+ if (hasHeader && index == 0) return null;
+ foreach (GroupInfo group in groups)
+ {
+ if (group.StartIndex <= index && group.StartIndex + group.Count > index)
+ {
+ Visited = group;
+ return group;
+ }
+ }
+ Visited = null;
+ return null;
+ }
+
+ /*
+ private object GetGroupParent(int index)
+ {
+ if (Visited != null)
+ {
+ if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
+ return Visited.GroupParent;
+ }
+ if (hasHeader && index == 0) return null;
+ foreach (GroupInfo group in groups)
+ {
+ if (group.StartIndex <= index && group.StartIndex + group.Count > index)
+ {
+ Visited = group;
+ return group.GroupParent;
+ }
+ }
+ Visited = null;
+ return null;
+ }
+ */
+
+ class GroupInfo
+ {
+ public object GroupParent;
+ public int StartIndex;
+ public int Count;
+ public float GroupSize;
+ public float GroupPosition;
+ //Items relative position from the GroupPosition
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// Default layout manager for CollectionView.
+ /// Lay out ViewItem and recycle ViewItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract class ItemsLayouter : ICollectionChangedNotifier, IDisposable
+ {
+ private bool disposed = false;
+
+ /// <summary>
+ /// Container which contains ViewItems.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected View Container{ get ; set; }
+
+ /// <summary>
+ /// Parent ItemsView.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected RecyclerView ItemsView{ get; set; }
+
+ /// <summary>
+ /// The last scrolled position which is calculated by ScrollableBase. The value should be updated in the Recycle() method.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected float PrevScrollPosition { get; set; }
+
+ /// <summary>
+ /// First index of visible items.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected int FirstVisible { get; set; } = -1;
+
+ /// <summary>
+ /// Last index of visible items.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected int LastVisible { get; set; } = -1;
+
+ /// <summary>
+ /// Visible ViewItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected List<RecyclerViewItem> VisibleItems { get; } = new List<RecyclerViewItem>();
+
+ /// <summary>
+ /// Flag of layouter initialization.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected bool IsInitialized { get; set; } = false;
+
+ /// <summary>
+ /// Candidate item step size for scroll size measure.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected float StepCandidate { get; set; }
+
+ /// <summary>
+ /// Content size of scrollable.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected float ScrollContentSize { get; set; }
+
+ /// <summary>
+ /// boolean flag of scrollable horizontal direction.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected bool IsHorizontal { get; set; }
+
+ /// <summary>
+ /// Clean up ItemsLayouter.
+ /// </summary>
+ /// <param name="view"> ItemsView of layouter.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void Initialize(RecyclerView view)
+ {
+ ItemsView = view ?? throw new ArgumentNullException(nameof(view));
+ Container = view.ContentContainer;
+ PrevScrollPosition = 0.0f;
+
+ IsHorizontal = (view.ScrollingDirection == ScrollableBase.Direction.Horizontal);
+
+ IsInitialized = true;
+ }
+
+ /// <summary>
+ /// This is called to find out where items are lain out according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <param name="force">boolean force flag to layouting forcely.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void RequestLayout(float scrollPosition, bool force = false)
+ {
+ // Layouting Items in scrollPosition.
+ }
+
+ /// <summary>
+ /// Clear the current screen and all properties.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void Clear()
+ {
+ foreach (RecyclerViewItem item in VisibleItems)
+ {
+ if (ItemsView != null) ItemsView.UnrealizeItem(item, false);
+ }
+ VisibleItems.Clear();
+ ItemsView = null;
+ Container = null;
+ }
+
+ /// <summary>
+ /// Position of layouting item.
+ /// </summary>
+ /// <param name="item">item of dataset.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual (float X, float Y) GetItemPosition(object item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ // Layouting Items in scrollPosition.
+ return (0, 0);
+ }
+
+ /// <summary>
+ /// Size of layouting item.
+ /// </summary>
+ /// <param name="item">item of dataset.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual (float X, float Y) GetItemSize(object item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ // Layouting Items in scrollPosition.
+ return (0, 0);
+ }
+
+ /// <summary>
+ /// This is called to find out how much container size can be.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual float CalculateLayoutOrientationSize()
+ {
+ return 0.0f;
+ }
+
+ /// <summary>
+ /// Adjust scrolling position by own scrolling rules.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual float CalculateCandidateScrollPosition(float scrollPosition)
+ {
+ return scrollPosition;
+ }
+
+ /// <summary>
+ /// Notify the relayout of ViewItem.
+ /// </summary>
+ /// <param name="item">updated ViewItem.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyItemSizeChanged(RecyclerViewItem item)
+ {
+ }
+
+ /// <summary>
+ /// Notify the dataset is Changed.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyDataSetChanged()
+ {
+ Initialize(ItemsView);
+ }
+
+ /// <summary>
+ /// Notify the observable item in startIndex is changed.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Changed item index.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyItemChanged(IItemSource source, int startIndex)
+ {
+ }
+
+ /// <summary>
+ /// Notify the observable item is inserted in dataset.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Inserted item index.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyItemInserted(IItemSource source, int startIndex)
+ {
+ }
+
+ /// <summary>
+ /// Notify the observable item is moved from fromPosition to ToPosition.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="fromPosition">Previous item position.</param>
+ /// <param name="toPosition">Moved item position.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
+ {
+ }
+
+ /// <summary>
+ /// Notify the range of observable items from start to end are changed.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startRange">Start index of changed items range.</param>
+ /// <param name="endRange">End index of changed items range.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyItemRangeChanged(IItemSource source, int startRange, int endRange)
+ {
+ }
+
+ /// <summary>
+ /// Notify the count range of observable items are inserted in startIndex.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Start index of inserted items range.</param>
+ /// <param name="count">The number of inserted items.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
+ {
+ }
+
+ /// <summary>
+ /// Notify the count range of observable items from the startIndex are removed.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Start index of removed items range.</param>
+ /// <param name="count">The number of removed items</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
+ {
+ }
+
+ /// <summary>
+ /// Notify the observable item in startIndex is removed.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Index of removed item.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void NotifyItemRemoved(IItemSource source, int startIndex)
+ {
+ }
+
+ /// <summary>
+ /// Gets the next keyboard focusable view in this control towards the given direction.<br />
+ /// A control needs to override this function in order to support two dimensional keyboard navigation.<br />
+ /// </summary>
+ /// <param name="currentFocusedView">The current focused view.</param>
+ /// <param name="direction">The direction to move the focus towards.</param>
+ /// <param name="loopEnabled">Whether the focus movement should be looped within the control.</param>
+ /// <returns>The next keyboard focusable view in this control or an empty handle if no view can be focused.</returns>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ {
+ return null;
+ }
+
+ /// <summary>
+ /// Dispose ItemsLayouter and all children on it.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Measure the size of chlid ViewItem manually.
+ /// </summary>
+ /// <param name="parent">Parent ItemsView.</param>
+ /// <param name="child">Child ViewItem to Measure()</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void MeasureChild(RecyclerView parent, RecyclerViewItem child)
+ {
+ if (parent == null) throw new ArgumentNullException(nameof(parent));
+ if (child == null) throw new ArgumentNullException(nameof(child));
+
+ if (child.Layout == null) return;
+
+ //FIXME: This measure can be restricted size of child to be less than parent size.
+ // but in some multiple-line TextLabel can be long enough to over the it's parent size.
+
+ MeasureSpecification childWidthMeasureSpec = LayoutGroup.GetChildMeasureSpecification(
+ new MeasureSpecification(new LayoutLength(parent.Size.Width), MeasureSpecification.ModeType.Exactly),
+ new LayoutLength(0),
+ new LayoutLength(child.WidthSpecification));
+
+ MeasureSpecification childHeightMeasureSpec = LayoutGroup.GetChildMeasureSpecification(
+ new MeasureSpecification(new LayoutLength(parent.Size.Height), MeasureSpecification.ModeType.Exactly),
+ new LayoutLength(0),
+ new LayoutLength(child.HeightSpecification));
+
+ child.Layout.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ /// <summary>
+ /// Find consecutive visible items index.
+ /// </summary>
+ /// <param name="visibleArea">float turple of visible area start position to end position. </param>
+ /// <return>int turple of start index to end index</return>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
+ {
+ return (0, 0);
+ }
+
+ /// <summary>
+ /// Dispose ItemsLayouter and all children on it.
+ /// </summary>
+ /// <param name="disposing">true when it disposed by Dispose(). </param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ {
+ return;
+ }
+
+ disposed = true;
+ if (disposing) Clear();
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.Linq;
+using Tizen.NUI.BaseComponents;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Components
+{
+
+
+ /// <summary>
+ /// [Draft] This class implements a linear box layout.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class LinearLayouter : ItemsLayouter
+ {
+ private readonly List<float> ItemPosition = new List<float>();
+ private readonly List<float> ItemSize = new List<float>();
+ private int ItemSizeChanged = -1;
+ private CollectionView colView;
+ private bool hasHeader;
+ private float headerSize;
+ private bool hasFooter;
+ private float footerSize;
+ private bool isGrouped;
+ private readonly List<GroupInfo> groups = new List<GroupInfo>();
+ private float groupHeaderSize;
+ private float groupFooterSize;
+ private GroupInfo Visited;
+
+ /// <summary>
+ /// Clean up ItemsLayouter.
+ /// </summary>
+ /// <param name="view"> ItemsView of layouter.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void Initialize(RecyclerView view)
+ {
+ colView = view as CollectionView;
+ if (colView == null)
+ {
+ 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();
+
+ FirstVisible = 0;
+ LastVisible = 0;
+
+ IsHorizontal = (colView.ScrollingDirection == ScrollableBase.Direction.Horizontal);
+
+ RecyclerViewItem header = colView?.Header;
+ RecyclerViewItem footer = colView?.Footer;
+ float width, height;
+ int count = colView.InternalItemSource.Count;
+
+ if (header != null)
+ {
+ MeasureChild(colView, header);
+
+ width = header.Layout != null ? header.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
+ height = header.Layout != null ? header.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
+
+ headerSize = IsHorizontal ? width : height;
+ hasHeader = true;
+
+ colView.UnrealizeItem(header);
+ }
+ else hasHeader = false;
+
+ if (footer != null)
+ {
+ MeasureChild(colView, footer);
+
+ width = footer.Layout != null ? footer.Layout.MeasuredWidth.Size.AsRoundedValue() : 0;
+ height = footer.Layout != null ? footer.Layout.MeasuredHeight.Size.AsRoundedValue() : 0;
+
+ footerSize = IsHorizontal ? width : height;
+ footer.Index = count - 1;
+ hasFooter = true;
+
+ colView.UnrealizeItem(footer);
+ }
+ else hasFooter = false;
+
+ //No Internal Source exist.
+ if (count == (hasHeader? (hasFooter? 2 : 1) : 0)) return;
+
+ int firstIndex = hasHeader ? 1 : 0;
+
+ if (colView.IsGrouped)
+ {
+ isGrouped = true;
+
+ if (colView.GroupHeaderTemplate != null)
+ {
+ while (!colView.InternalItemSource.IsGroupHeader(firstIndex)) firstIndex++;
+ //must be always true
+ if (colView.InternalItemSource.IsGroupHeader(firstIndex))
+ {
+ RecyclerViewItem groupHeader = colView.RealizeItem(firstIndex);
+ firstIndex++;
+
+ if (groupHeader == null) throw new Exception("["+firstIndex+"] Group Header failed to realize!");
+
+ // Need to Set proper hieght or width on scroll direciton.
+ if (groupHeader.Layout == null)
+ {
+ width = groupHeader.WidthSpecification;
+ height = groupHeader.HeightSpecification;
+ }
+ else
+ {
+ MeasureChild(colView, groupHeader);
+
+ width = groupHeader.Layout.MeasuredWidth.Size.AsRoundedValue();
+ height = groupHeader.Layout.MeasuredHeight.Size.AsRoundedValue();
+ }
+ //Console.WriteLine("[NUI] GroupHeader Size {0} :{0}", width, height);
+ // pick the StepCandidate.
+ groupHeaderSize = IsHorizontal ? width : height;
+ colView.UnrealizeItem(groupHeader);
+ }
+ }
+ else
+ {
+ groupHeaderSize = 0F;
+ }
+
+ if (colView.GroupFooterTemplate != null)
+ {
+ int firstFooter = firstIndex;
+ while (!colView.InternalItemSource.IsGroupFooter(firstFooter)) firstFooter++;
+ //must be always true
+ if (colView.InternalItemSource.IsGroupFooter(firstFooter))
+ {
+ RecyclerViewItem groupFooter = colView.RealizeItem(firstFooter);
+
+ if (groupFooter == null) throw new Exception("["+firstFooter+"] Group Footer failed to realize!");
+ // Need to Set proper hieght or width on scroll direciton.
+ if (groupFooter.Layout == null)
+ {
+ width = groupFooter.WidthSpecification;
+ height = groupFooter.HeightSpecification;
+ }
+ else
+ {
+ MeasureChild(colView, groupFooter);
+
+ width = groupFooter.Layout.MeasuredWidth.Size.AsRoundedValue();
+ height = groupFooter.Layout.MeasuredHeight.Size.AsRoundedValue();
+ }
+ // pick the StepCandidate.
+ groupFooterSize = IsHorizontal ? width : height;
+
+ colView.UnrealizeItem(groupFooter);
+ }
+ }
+ else
+ {
+ groupFooterSize = 0F;
+ }
+ }
+ else isGrouped = false;
+
+ bool failed = false;
+ //Final Check of FirstIndex
+ while (colView.InternalItemSource.IsHeader(firstIndex) ||
+ colView.InternalItemSource.IsGroupHeader(firstIndex) ||
+ colView.InternalItemSource.IsGroupFooter(firstIndex))
+ {
+ if (colView.InternalItemSource.IsFooter(firstIndex))
+ {
+ StepCandidate = 0F;
+ failed = true;
+ break;
+ }
+ firstIndex++;
+ }
+
+ if (!failed)
+ {
+ RecyclerViewItem sizeDeligate = colView.RealizeItem(firstIndex);
+ if (sizeDeligate == null)
+ {
+ // error !
+ throw new Exception("Cannot create content from DatTemplate.");
+ }
+
+ sizeDeligate.BindingContext = colView.InternalItemSource.GetItem(firstIndex);
+
+ // Need to Set proper hieght or width on scroll direciton.
+ if (sizeDeligate.Layout == null)
+ {
+ width = sizeDeligate.WidthSpecification;
+ height = sizeDeligate.HeightSpecification;
+ }
+ else
+ {
+ MeasureChild(colView, sizeDeligate);
+
+ width = sizeDeligate.Layout.MeasuredWidth.Size.AsRoundedValue();
+ height = sizeDeligate.Layout.MeasuredHeight.Size.AsRoundedValue();
+ }
+ //Console.WriteLine("[NUI] Layout Size {0} :{0}", width, height);
+ // pick the StepCandidate.
+ StepCandidate = IsHorizontal ? width : height;
+ if (StepCandidate == 0) StepCandidate = 1; //????
+
+ colView.UnrealizeItem(sizeDeligate);
+ }
+
+ float Current = 0.0F;
+ IGroupableItemSource source = colView.InternalItemSource;
+ GroupInfo currentGroup = null;
+ for (int i = 0; i < count; i++)
+ {
+ if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+ {
+ if (i == 0 && hasHeader)
+ ItemSize.Add(headerSize);
+ else if (i == count -1 && hasFooter)
+ ItemSize.Add(footerSize);
+ else if (source.IsGroupHeader(i))
+ ItemSize.Add(groupHeaderSize);
+ else if (source.IsGroupFooter(i))
+ ItemSize.Add(groupFooterSize);
+ else ItemSize.Add(StepCandidate);
+ }
+ if (isGrouped)
+ {
+ if (i == 0 && hasHeader)
+ {
+ //ItemPosition.Add(Current);
+ Current += headerSize;
+ }
+ else if (i == count -1 && hasFooter)
+ {
+ //ItemPosition.Add(Current);
+ Current += footerSize;
+ }
+ else
+ {
+ //GroupHeader must always exist in group usage.
+ if (source.IsGroupHeader(i))
+ {
+ currentGroup = new GroupInfo()
+ {
+ GroupParent = source.GetGroupParent(i),
+ //hasHeader = true,
+ //hasFooter = false,
+ StartIndex = i,
+ Count = 1,
+ GroupSize = groupHeaderSize,
+ GroupPosition = Current
+ };
+ currentGroup.ItemPosition.Add(0);
+ groups.Add(currentGroup);
+ Current += groupHeaderSize;
+ }
+ //optional
+ else if (source.IsGroupFooter(i))
+ {
+ //currentGroup.hasFooter = true;
+ currentGroup.Count++;
+ currentGroup.GroupSize += groupFooterSize;
+ currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
+ Current += groupFooterSize;
+ }
+ else
+ {
+ currentGroup.Count++;
+ currentGroup.GroupSize += StepCandidate;
+ currentGroup.ItemPosition.Add(Current - currentGroup.GroupPosition);
+ Current += StepCandidate;
+ }
+ }
+ }
+ else
+ {
+ ItemPosition.Add(Current);
+
+ if (i == 0 && hasHeader) Current += headerSize;
+ else if (i == count -1 && hasFooter) Current += footerSize;
+ else Current += StepCandidate;
+ }
+ }
+
+ ScrollContentSize = Current;
+ if (IsHorizontal) colView.ContentContainer.SizeWidth = ScrollContentSize;
+ else colView.ContentContainer.SizeHeight = ScrollContentSize;
+
+
+ base.Initialize(view);
+ //Console.WriteLine("[NUI] Init Done, StepCnadidate{0}, Scroll{1}", StepCandidate, ScrollContentSize);
+ }
+
+ /// <summary>
+ /// This is called to find out where items are lain out according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <param name="force">boolean force flag to layouting forcely.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void RequestLayout(float scrollPosition, bool force = false)
+ {
+ // Layouting is only possible after once it intialized.
+ if (!IsInitialized) return;
+ int LastIndex = colView.InternalItemSource.Count -1;
+
+ if (!force && PrevScrollPosition == Math.Abs(scrollPosition)) return;
+ PrevScrollPosition = Math.Abs(scrollPosition);
+
+ if (ItemSizeChanged >= 0)
+ {
+ for (int i = ItemSizeChanged; i <= LastIndex; i++)
+ UpdatePosition(i);
+ ScrollContentSize = ItemPosition[LastIndex - 1] + GetItemSize(LastIndex);
+ }
+
+ int prevFirstVisible = FirstVisible;
+ int prevLastVisible = LastVisible;
+
+ (float X, float Y) visibleArea = (PrevScrollPosition,
+ PrevScrollPosition + ( IsHorizontal ? colView.Size.Width : colView.Size.Height)
+ );
+
+ // 1. Set First/Last Visible Item Index.
+ (int start, int end) = FindVisibleItems(visibleArea);
+ FirstVisible = start;
+ LastVisible = end;
+
+ // 2. Unrealize invisible items.
+ List<RecyclerViewItem> unrealizedItems = new List<RecyclerViewItem>();
+ foreach (RecyclerViewItem item in VisibleItems)
+ {
+ if (item.Index < FirstVisible || item.Index > LastVisible)
+ {
+ //Console.WriteLine("[NUI] Unrealize{0}!", item.Index);
+ unrealizedItems.Add(item);
+ colView.UnrealizeItem(item);
+ }
+ }
+ VisibleItems.RemoveAll(unrealizedItems.Contains);
+
+ // 3. Realize and placing visible items.
+ for (int i = FirstVisible; i <= LastVisible; i++)
+ {
+ RecyclerViewItem item = null;
+ // 4. Get item if visible or realize new.
+ if (i >= prevFirstVisible && i <= prevLastVisible)
+ {
+ item = GetVisibleItem(i);
+ if (item) continue;
+ }
+ if (item == null) item = colView.RealizeItem(i);
+
+ VisibleItems.Add(item);
+
+ // 5. Placing item.
+ float posX = 0F, posY = 0F;
+ if (isGrouped)
+ {
+ //isHeader?
+ if (colView.Header == item)
+ {
+ posX = 0F;
+ posY = 0F;
+ }
+ else if (colView.Footer == item)
+ {
+ posX = (IsHorizontal ? ScrollContentSize - item.SizeWidth : 0F);
+ posY =(IsHorizontal ? 0F : ScrollContentSize - item.SizeHeight);
+ }
+ else
+ {
+ GroupInfo gInfo = GetGroupInfo(i);
+ posX = (IsHorizontal ? gInfo.GroupPosition + gInfo.ItemPosition[i - gInfo.StartIndex] : 0F);
+ posY = (IsHorizontal ? 0F : gInfo.GroupPosition + gInfo.ItemPosition[i - gInfo.StartIndex]);
+ }
+ }
+ else
+ {
+ posX = (IsHorizontal ? ItemPosition[i] : 0F);
+ posY = (IsHorizontal ? 0F : ItemPosition[i]);
+ }
+
+ item.Position = new Position(posX, posY);
+ //Console.WriteLine("[NUI] ["+item+"]["+item.Index+"] :: ["+item.Position.X+", "+item.Position.Y+"] ==== \n");
+ }
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override (float X, float Y) GetItemPosition(object item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ // Layouting Items in scrollPosition.
+ float pos = ItemPosition[colView.InternalItemSource.GetPosition(item)];
+
+ return (IsHorizontal ? (pos, 0.0F) : (0.0F, pos));
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override (float X, float Y) GetItemSize(object item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ // Layouting Items in scrollPosition.
+ float size = GetItemSize(colView.InternalItemSource.GetPosition(item));
+ float view = (IsHorizontal ? colView.Size.Height : colView.Size.Width);
+
+ return (IsHorizontal ? (size, view) : (view, size));
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void NotifyItemSizeChanged(RecyclerViewItem item)
+ {
+ if (item == null)
+ throw new ArgumentNullException(nameof(item));
+
+ if (!IsInitialized ||
+ (colView.SizingStrategy == ItemSizingStrategy.MeasureFirst &&
+ item.Index != 0) ||
+ (item.Index < 0))
+ return;
+
+ float PrevSize, CurrentSize;
+ if (item.Index == (colView.InternalItemSource.Count-1))
+ {
+ PrevSize = ScrollContentSize - ItemPosition[item.Index];
+ }
+ else
+ {
+ PrevSize = ItemPosition[item.Index + 1] - ItemPosition[item.Index];
+ }
+
+ CurrentSize = (IsHorizontal ? item.Size.Width : item.Size.Height);
+
+ if (CurrentSize != PrevSize)
+ {
+ if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+ ItemSize[item.Index] = CurrentSize;
+ else
+ StepCandidate = CurrentSize;
+ }
+ if (ItemSizeChanged == -1) ItemSizeChanged = item.Index;
+ else ItemSizeChanged = Math.Min(ItemSizeChanged, item.Index);
+
+ //ScrollContentSize += Diff; UpdateOnce?
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override float CalculateLayoutOrientationSize()
+ {
+ //Console.WriteLine("[NUI] Calculate Layout ScrollContentSize {0}", ScrollContentSize);
+ return ScrollContentSize;
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override float CalculateCandidateScrollPosition(float scrollPosition)
+ {
+ //Console.WriteLine("[NUI] Calculate Candidate ScrollContentSize {0}", ScrollContentSize);
+ return scrollPosition;
+ }
+
+ /// <Inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ {
+ if (currentFocusedView == null)
+ throw new ArgumentNullException(nameof(currentFocusedView));
+
+ View nextFocusedView = null;
+ int targetSibling = -1;
+
+ switch(direction)
+ {
+ case View.FocusDirection.Left :
+ {
+ targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
+ break;
+ }
+ case View.FocusDirection.Right :
+ {
+ targetSibling = IsHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
+ break;
+ }
+ case View.FocusDirection.Up :
+ {
+ targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
+ break;
+ }
+ case View.FocusDirection.Down :
+ {
+ targetSibling = IsHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
+ break;
+ }
+ }
+
+ if(targetSibling > -1 && targetSibling < Container.Children.Count)
+ {
+ RecyclerViewItem candidate = Container.Children[targetSibling] as RecyclerViewItem;
+ if(candidate.Index >= 0 && candidate.Index < colView.InternalItemSource.Count)
+ {
+ nextFocusedView = candidate;
+ }
+ }
+
+ return nextFocusedView;
+ }
+
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override (int start, int end) FindVisibleItems((float X, float Y) visibleArea)
+ {
+ int MaxIndex = colView.InternalItemSource.Count - 1 - (hasFooter ? 1 : 0);
+ int adds = 5;
+ int skipGroup = -2;
+ (int start, int end) found = (0, 0);
+
+ // 1. Find the start index.
+ // Header is Showing
+ if (hasHeader && visibleArea.X <= headerSize)
+ {
+ found.start = 0;
+ }
+ else
+ {
+ if (isGrouped)
+ {
+ bool failed = true;
+ foreach(GroupInfo gInfo in groups)
+ {
+ skipGroup++;
+ // in the Group
+ if (gInfo.GroupPosition <= visibleArea.X &&
+ gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.X)
+ {
+ for (int i = 0; i < gInfo.Count; i++)
+ {
+ // Reach last index of group.
+ if (i == (gInfo.Count - 1))
+ {
+ found.start = gInfo.StartIndex + i - adds;
+ failed = false;
+ break;
+
+ }
+ else if (gInfo.ItemPosition[i] <= visibleArea.X - gInfo.GroupPosition &&
+ gInfo.ItemPosition[i + 1] >= visibleArea.X - gInfo.GroupPosition)
+ {
+ found.start = gInfo.StartIndex + i - adds;
+ failed = false;
+ break;
+ }
+ }
+ }
+ }
+ //footer only shows?
+ if (failed)
+ {
+ found.start = MaxIndex;
+ }
+ }
+ else
+ {
+ float visibleAreaX = visibleArea.X - (hasHeader ? headerSize : 0);
+ found.start = (Convert.ToInt32(Math.Abs(visibleAreaX / StepCandidate)) - adds);
+ }
+
+ if (found.start < 0) found.start = 0;
+ }
+
+ if (hasFooter && visibleArea.Y > ScrollContentSize - footerSize)
+ {
+ found.end = MaxIndex + 1;
+ }
+ else
+ {
+ if (isGrouped)
+ {
+ bool failed = true;
+ // can it be start from founded group...?
+ //foreach(GroupInfo gInfo in groups.Skip(skipGroup))
+ foreach(GroupInfo gInfo in groups)
+ {
+ // in the Group
+ if (gInfo.GroupPosition <= visibleArea.Y &&
+ gInfo.GroupPosition + gInfo.GroupSize >= visibleArea.Y)
+ {
+ for (int i = 0; i < gInfo.Count; i++)
+ {
+ if (i == (gInfo.Count - 1))
+ {
+ //Sould be groupFooter!
+ found.end = gInfo.StartIndex + i + adds;
+ failed = false;
+ break;
+
+ }
+ else if (gInfo.ItemPosition[i] <= visibleArea.Y - gInfo.GroupPosition &&
+ gInfo.ItemPosition[i + 1] >= visibleArea.Y - gInfo.GroupPosition)
+ {
+ found.end = gInfo.StartIndex + i + adds;
+ failed = false;
+ break;
+ }
+ }
+ }
+ }
+ if (failed) found.end = MaxIndex;
+ }
+ else
+ {
+ float visibleAreaY = visibleArea.Y - (hasHeader ? headerSize : 0);
+ found.end = (Convert.ToInt32(Math.Abs(visibleAreaY / StepCandidate)) + adds);
+ if (hasHeader) found.end += 1;
+ }
+ if (found.end > (MaxIndex)) found.end = MaxIndex;
+ }
+ return found;
+ }
+
+ private float GetItemSize(int index)
+ {
+ if (colView.SizingStrategy == ItemSizingStrategy.MeasureAll)
+ {
+ return ItemSize[index];
+ }
+ else
+ {
+ if (index == 0 && hasHeader)
+ return headerSize;
+ if (index == colView.InternalItemSource.Count - 1 && hasFooter)
+ return footerSize;
+ return StepCandidate;
+ }
+ }
+
+ private void UpdatePosition(int index)
+ {
+ bool IsGroup = (colView.InternalItemSource is IGroupableItemSource);
+
+ if (index <= 0) return;
+ if (index >= colView.InternalItemSource.Count)
+
+ if (IsGroup)
+ {
+ //IsGroupHeader = (colView.InternalItemSource as IGroupableItemSource).IsGroupHeader(index);
+ //IsGroupFooter = (colView.InternalItemSource as IGroupableItemSource).IsGroupFooter(index);
+ //Do Something
+ }
+
+ ItemPosition[index] = ItemPosition[index-1] + GetItemSize(index-1);
+ }
+
+ private RecyclerViewItem GetVisibleItem(int index)
+ {
+ foreach (RecyclerViewItem item in VisibleItems)
+ {
+ if (item.Index == index) return item;
+ }
+ return null;
+ }
+
+ private GroupInfo GetGroupInfo(int index)
+ {
+ if (Visited != null)
+ {
+ if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
+ return Visited;
+ }
+ if (hasHeader && index == 0) return null;
+ foreach (GroupInfo group in groups)
+ {
+ if (group.StartIndex <= index && group.StartIndex + group.Count > index)
+ {
+ Visited = group;
+ return group;
+ }
+ }
+ Visited = null;
+ return null;
+ }
+/*
+ private object GetGroupParent(int index)
+ {
+ if (Visited != null)
+ {
+ if (Visited.StartIndex <= index && Visited.StartIndex + Visited.Count > index)
+ return Visited.GroupParent;
+ }
+ if (hasHeader && index == 0) return null;
+ foreach (GroupInfo group in groups)
+ {
+ if (group.StartIndex <= index && group.StartIndex + group.Count > index)
+ {
+ Visited = group;
+ return group.GroupParent;
+ }
+ }
+ Visited = null;
+ return null;
+ }
+*/
+ class GroupInfo
+ {
+ public object GroupParent;
+ public int StartIndex;
+ public int Count;
+ public float GroupSize;
+ public float GroupPosition;
+ //Items relative position from the GroupPosition
+ public List<float> ItemPosition = new List<float>();
+ }
+ }
+}
+++ /dev/null
-/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-using System;
-using Tizen.NUI.BaseComponents;
-using System.Collections.Generic;
-using System.ComponentModel;
-
-namespace Tizen.NUI.Components
-{
- /// <summary>
- /// [Draft] This class implements a linear box layout.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public class LinearRecycleLayoutManager : RecycleLayoutManager
- {
- private int firstVisibleItemIndex = -1;
- private int lastVisibleItemIndex = -1;
-
- private bool IsItemVisible(float scrollPosition, RecycleItem item)
- {
- bool result = false;
- View list = Container.GetParent() as View;
- if (list == null)
- {
- return result;
- }
-
- Vector2 visibleArea = new Vector2(Math.Abs(scrollPosition),
- Math.Abs(scrollPosition) + (LayoutOrientation == Orientation.Horizontal ?
- list.Size.Width : list.Size.Height)
- );
-
- float firstCheckPoint = LayoutOrientation == Orientation.Horizontal ? item.Position.X : item.Position.Y;
- float secondCheckPoint = LayoutOrientation == Orientation.Horizontal ?
- firstCheckPoint + item.Size.Width :
- firstCheckPoint + item.Size.Height;
-
- // Tizen.Log.Error("NUI", "[1p] "+visibleArea.X+ " =< "+firstCheckPoint+" =< "+visibleArea.Y+" ==== \n");
- // Tizen.Log.Error("NUI", "[2p] "+visibleArea.X+ " =< "+secondCheckPoint+" =< "+visibleArea.Y+" ==== \n");
-
- result = (firstCheckPoint >= visibleArea.X && firstCheckPoint <= visibleArea.Y) || (secondCheckPoint >= visibleArea.X && secondCheckPoint <= visibleArea.Y);
-
- return result;
- }
-
- /// <summary>
- /// This is called to find out where items are lain out according to current scroll position.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- public override void Layout(float scrollPosition)
- {
- firstVisibleItemIndex = -1;
- lastVisibleItemIndex = -1;
-
- RecycleItem previousItem = null;
-
- for (int i = 0; i < Container.Children.Count; i++)
- {
- RecycleItem item = Container.Children[i] as RecycleItem;
-
- if (previousItem != null && item != null)
- {
- item.Position = LayoutOrientation == Orientation.Horizontal ?
- new Position(
- previousItem.Position.X + (previousItem.CurrentSize.Width != 0 ?
- previousItem.CurrentSize.Width :
- previousItem.Size.Width),
- item.PositionY
- ) :
- new Position(
- item.PositionX,
- previousItem.Position.Y + (previousItem.CurrentSize.Height != 0 ?
- previousItem.CurrentSize.Height :
- previousItem.Size.Height)
- );
- }
-
- bool isVisible = IsItemVisible(scrollPosition, item);
-
- if (isVisible)
- {
- firstVisibleItemIndex = firstVisibleItemIndex == -1 ? i : firstVisibleItemIndex;
- lastVisibleItemIndex = i;
- }
-
- previousItem = item;
-
- // Tizen.Log.Error("NUI","["+item.DataIndex+"] "+item.Position.Y+" ==== \n");
- }
-
- if (StepSize == 0)
- {
- StepSize = LayoutOrientation == Orientation.Horizontal ? ItemSize.Width : ItemSize.Height;
- }
- }
-
- /// <summary>
- /// This is called to find out how much container size can be.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override float CalculateLayoutOrientationSize()
- {
- return StepSize * DataCount;
- }
-
- /// <summary>
- /// This is called to find out which items should be recycled according to current scroll position.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <returns>List of RecycleItems which should be recycled.</returns>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- public override List<RecycleItem> Recycle(float scrollPosition)
- {
- List<RecycleItem> result = new List<RecycleItem>();
-
- bool checkFront = (PrevScrollPosition - scrollPosition) > 0;
-
- if (checkFront)
- {
- if (firstVisibleItemIndex > 3)
- {
- // Too many item is in front!!! move first item to back!!!!
- RecycleItem target = Container.Children[0] as RecycleItem;
- if (target != null)
- {
- target.DataIndex = target.DataIndex + Container.Children.Count;
- target.SiblingOrder = Container.Children.Count - 1;
-
- result.Add(target);
- }
- }
- }
- else
- {
- if (lastVisibleItemIndex < Container.Children.Count - 3)
- {
- RecycleItem prevFirstItem = Container.Children[0] as RecycleItem;
- RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem;
- if (prevFirstItem != null && target != null)
- {
- target.Position = new Position(
- LayoutOrientation == Orientation.Horizontal ? (prevFirstItem.Position.X - target.Size.Width) : prevFirstItem.Position.X,
- LayoutOrientation == Orientation.Horizontal ? prevFirstItem.Position.Y : (prevFirstItem.Position.Y - target.Size.Height)
- );
- target.DataIndex = target.DataIndex - Container.Children.Count;
- target.SiblingOrder = 0;
-
- result.Add(target);
- }
- }
- }
-
- PrevScrollPosition = scrollPosition;
-
- return result;
- }
-
- /// <summary>
- /// Adjust scrolling position by own scrolling rules.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- public override float CalculateCandidateScrollPosition(float scrollPosition)
- {
- return scrollPosition;
- }
-
- public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
- {
- View nextFocusedView = null;
- int targetSibling = -1;
- bool isHorizontal = LayoutOrientation == Orientation.Horizontal;
-
- switch (direction)
- {
- case View.FocusDirection.Left:
- {
- targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
- break;
- }
- case View.FocusDirection.Right:
- {
- targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
- break;
- }
- case View.FocusDirection.Up:
- {
- targetSibling = isHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
- break;
- }
- case View.FocusDirection.Down:
- {
- targetSibling = isHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
- break;
- }
- }
-
- if (targetSibling > -1 && targetSibling < Container.Children.Count)
- {
- RecycleItem candidate = Container.Children[targetSibling] as RecycleItem;
- if (candidate != null && candidate.DataIndex >= 0 && candidate.DataIndex < DataCount)
- {
- nextFocusedView = candidate;
- }
- }
-
- return nextFocusedView;
- }
- }
-}
+++ /dev/null
-/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-
-namespace Tizen.NUI.Components
-{
- /// <summary>
- /// [Draft] Defalt adapter for RecyclerView.
- /// Managing RecycleItem and Data for RecyclerView.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public class RecycleAdapter
- {
- private List<object> mData = new List<object>();
-
- /// <summary>
- /// Create recycle item for RecyclerView.
- /// RecyclerView will make its children using this api.
- /// </summary>
- /// <returns>Item for RecyclerView</returns>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual RecycleItem CreateRecycleItem()
- {
- return new RecycleItem();
- }
-
- /// <summary>
- /// Bind data with recycler item.
- /// This function is called when RecyclerItem is used again with new data.
- /// Can update content of recycle item with new data at DataIndex of item.
- /// </summary>
- /// <param name="item">Reused RecycleItem which needs data binding.</param>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual void BindData(RecycleItem item)
- {
-
- }
-
- /// <summary>
- /// Notify when data of adapter is changed.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void Notify()
- {
- OnDataChanged?.Invoke(this, new EventArgs());
- }
-
- /// <summary>
- /// Triggered when user called Notify().
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public event EventHandler<EventArgs> OnDataChanged;
-
- /// <summary>
- /// Triggered when user called Notify().
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public List<object> Data
- {
- get
- {
- return mData;
- }
- set
- {
- mData = value;
- Notify();
- }
- }
- }
-}
+++ /dev/null
-/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-using System.ComponentModel;
-
-namespace Tizen.NUI.Components
-{
- /// <summary>
- /// [Draft] This class provides a basic item for RecyclerView.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public class RecycleItem : Control
- {
- /// <summary>
- /// Data index which is binded to item by RecycleAdapter.
- /// Can access to data of RecycleAdapter using this index.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public int DataIndex { get; set; } = 0;
- }
-}
+++ /dev/null
-/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-using Tizen.NUI.BaseComponents;
-using System.Collections.Generic;
-using System.ComponentModel;
-
-namespace Tizen.NUI.Components
-{
- /// <summary>
- /// [Draft] Defalt layout manager for RecyclerView.
- /// Lay out RecycleItem and recycle RecycleItem.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public class RecycleLayoutManager
- {
- /// <summary>
- /// Enumeration for the direction in which the content is laid out
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public enum Orientation
- {
- /// <summary>
- /// Vertical
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- Vertical = 0,
- /// <summary>
- /// Horizontal
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- Horizontal = 1,
- }
-
- /// <summary>
- /// Container which contains RecycleItems.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public View Container { get; set; }
-
- /// <summary>
- /// Size of RecycleItem.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Size ItemSize { get; set; } = new Size();
-
- /// <summary>
- /// Get/Set the orientation in the layout.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Orientation LayoutOrientation { get; set; } = Orientation.Vertical;
-
- /// <summary>
- /// How far can you reach the next item.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public float StepSize { get; protected set; }
-
- /// <summary>
- /// How far can you reach the next item.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public int DataCount { get; set; }
-
- /// <summary>
- /// The last scrolled position which is calculated by ScrollableBase. The value should be updated in the Recycle() method.
- /// </summary>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- protected float PrevScrollPosition { get; set; }
-
- /// <summary>
- /// This is called to find out where items are lain out according to current scroll position.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual void Layout(float scrollPosition)
- {
-
- }
-
- /// <summary>
- /// This is called to find out how much container size can be.
- /// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual float CalculateLayoutOrientationSize()
- {
- return 0.0f;
- }
-
- /// <summary>
- /// This is called to find out which items should be recycled according to current scroll position.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <returns>List of RecycleItems which should be recycled.</returns>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual List<RecycleItem> Recycle(float scrollPosition)
- {
- return new List<RecycleItem>();
- }
-
- /// <summary>
- /// Adjust scrolling position by own scrolling rules.
- /// </summary>
- /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual float CalculateCandidateScrollPosition(float scrollPosition)
- {
- return scrollPosition;
- }
-
- /// <summary>
- /// Gets the next keyboard focusable view in this control towards the given direction.<br />
- /// A control needs to override this function in order to support two dimensional keyboard navigation.<br />
- /// </summary>
- /// <param name="currentFocusedView">The current focused view.</param>
- /// <param name="direction">The direction to move the focus towards.</param>
- /// <param name="loopEnabled">Whether the focus movement should be looped within the control.</param>
- /// <returns>The next keyboard focusable view in this control or an empty handle if no view can be focused.</returns>
- [EditorBrowsable(EditorBrowsableState.Never)]
- public virtual View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
- {
- return null;
- }
-
- }
-}
-/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
*/
using System;
-using Tizen.NUI.BaseComponents;
+using System.Linq;
+using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
namespace Tizen.NUI.Components
{
/// <summary>
- /// [Draft] This class provides a View that can recycle items to improve performance.
+ /// [Draft] This class provides a View that can layouting items in list and grid with high performance.
/// </summary>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
[EditorBrowsable(EditorBrowsableState.Never)]
- public class RecyclerView : ScrollableBase
+ public abstract class RecyclerView : ScrollableBase, ICollectionChangedNotifier
{
- private RecycleAdapter adapter;
- private RecycleLayoutManager layoutManager;
- private int totalItemCount = 15;
- private List<PropertyNotification> notifications = new List<PropertyNotification>();
-
+ /// <summary>
+ /// Base Constructor
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
public RecyclerView() : base()
{
- Initialize(new RecycleAdapter(), new RecycleLayoutManager());
+ Scrolling += OnScrolling;
}
/// <summary>
- /// Default constructor.
+ /// Item's source data.
/// </summary>
- /// <param name="adapter">Recycle adapter of RecyclerView.</param>
- /// <param name="layoutManager">Recycle layoutManager of RecyclerView.</param>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
[EditorBrowsable(EditorBrowsableState.Never)]
- public RecyclerView(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
- {
- Initialize(adapter, layoutManager);
- }
+ public virtual IEnumerable ItemsSource { get; set; }
- private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
- {
- FocusGroup = true;
- SetKeyboardNavigationSupport(true);
- Scrolling += OnScrolling;
+ /// <summary>
+ /// DataTemplate for items.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual DataTemplate ItemTemplate { get; set; }
- this.adapter = adapter;
- this.adapter.OnDataChanged += OnAdapterDataChanged;
+ /// <summary>
+ /// Internal encapsulated items data source.
+ /// </summary>
+ internal IItemSource InternalItemSource { get; set;}
- this.layoutManager = layoutManager;
- this.layoutManager.Container = ContentContainer;
- this.layoutManager.ItemSize = this.adapter.CreateRecycleItem().Size;
- this.layoutManager.DataCount = this.adapter.Data.Count;
+ /// <summary>
+ /// RecycleCache of ViewItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected List<RecyclerViewItem> RecycleCache { get; } = new List<RecyclerViewItem>();
- InitializeItems();
- }
+ /// <summary>
+ /// Internal Items Layouter.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected ItemsLayouter InternalItemsLayouter {get; set; }
- private void OnItemSizeChanged(object source, PropertyNotification.NotifyEventArgs args)
- {
- layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
- }
+ /// <summary>
+ /// Max size of RecycleCache. Default is 50.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected int CacheMax { get; set; } = 50;
- public int TotalItemCount
+ /// <inheritdoc/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void OnRelayout(Vector2 size, RelayoutContainer container)
{
- get
- {
- return totalItemCount;
- }
- set
+ //Console.WriteLine("[NUI] On ReLayout [{0} {0}]", size.X, size.Y);
+ base.OnRelayout(size, container);
+ if (InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
{
- totalItemCount = value;
- InitializeItems();
+ InternalItemsLayouter.Initialize(this);
+ InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y, true);
}
}
- private void InitializeItems()
+ /// <summary>
+ /// Notify Dataset is Changed.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void NotifyDataSetChanged()
{
- for (int i = Children.Count - 1; i > -1; i--)
- {
- Children[i].Unparent();
- notifications[i].Notified -= OnItemSizeChanged;
- notifications.RemoveAt(i);
- }
-
- for (int i = 0; i < totalItemCount; i++)
+ //Need to update view.
+ if (InternalItemsLayouter != null)
{
- RecycleItem item = adapter.CreateRecycleItem();
- item.DataIndex = i;
- item.Name = "[" + i + "] recycle";
-
- if (i < adapter.Data.Count)
+ InternalItemsLayouter.NotifyDataSetChanged();
+ if (ScrollingDirection == Direction.Horizontal)
{
- adapter.BindData(item);
+ ContentContainer.SizeWidth =
+ InternalItemsLayouter.CalculateLayoutOrientationSize();
+ }
+ else
+ {
+ ContentContainer.SizeHeight =
+ InternalItemsLayouter.CalculateLayoutOrientationSize();
}
- Add(item);
-
- PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f));
- noti.Notified += OnItemSizeChanged;
- notifications.Add(noti);
}
+ }
- layoutManager.Layout(0.0f);
-
- if (ScrollingDirection == Direction.Horizontal)
- {
- ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
- }
- else
+ /// <summary>
+ /// Notify observable item is changed.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Changed item index.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void NotifyItemChanged(IItemSource source, int startIndex)
+ {
+ if (InternalItemsLayouter != null)
{
- ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
+ InternalItemsLayouter.NotifyItemChanged(source, startIndex);
}
}
-
- public new Direction ScrollingDirection
+ /// <summary>
+ /// Notify observable item is inserted in dataset.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Inserted item index.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void NotifyItemInserted(IItemSource source, int startIndex)
{
- get
+ if (InternalItemsLayouter != null)
{
- return base.ScrollingDirection;
- }
- set
- {
- base.ScrollingDirection = value;
-
- if (ScrollingDirection == Direction.Horizontal)
- {
- ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
- }
- else
- {
- ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
- }
+ InternalItemsLayouter.NotifyItemInserted(source, startIndex);
}
}
/// <summary>
- /// Recycler adpater.
+ /// Notify observable item is moved from fromPosition to ToPosition.
/// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ /// <param name="source">Dataset source.</param>
+ /// <param name="fromPosition">Previous item position.</param>
+ /// <param name="toPosition">Moved item position.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
- public RecycleAdapter Adapter
+ public void NotifyItemMoved(IItemSource source, int fromPosition, int toPosition)
{
- get
+ if (InternalItemsLayouter != null)
{
- return adapter;
+ InternalItemsLayouter.NotifyItemMoved(source, fromPosition, toPosition);
}
- set
- {
- if (adapter != null)
- {
- adapter.OnDataChanged -= OnAdapterDataChanged;
- }
+ }
- adapter = value;
- adapter.OnDataChanged += OnAdapterDataChanged;
- layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
- layoutManager.DataCount = adapter.Data.Count;
- InitializeItems();
+ /// <summary>
+ /// Notify range of observable items from start to end are changed.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="start">Start index of changed items range.</param>
+ /// <param name="end">End index of changed items range.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void NotifyItemRangeChanged(IItemSource source, int start, int end)
+ {
+ if (InternalItemsLayouter != null)
+ {
+ InternalItemsLayouter.NotifyItemRangeChanged(source, start, end);
}
}
/// <summary>
- /// Recycler layoutManager.
+ /// Notify count range of observable count items are inserted in startIndex.
/// </summary>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Start index of inserted items range.</param>
+ /// <param name="count">The number of inserted items.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
- public RecycleLayoutManager LayoutManager
+ public void NotifyItemRangeInserted(IItemSource source, int startIndex, int count)
{
- get
+ if (InternalItemsLayouter != null)
{
- return layoutManager;
+ InternalItemsLayouter.NotifyItemRangeInserted(source, startIndex, count);
}
- set
+ }
+
+ /// <summary>
+ /// Notify the count range of observable items from the startIndex are removed.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Start index of removed items range.</param>
+ /// <param name="count">The number of removed items</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void NotifyItemRangeRemoved(IItemSource source, int startIndex, int count)
+ {
+ if (InternalItemsLayouter != null)
{
- layoutManager = value;
- layoutManager.Container = ContentContainer;
- layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
- layoutManager.DataCount = adapter.Data.Count;
- InitializeItems();
+ InternalItemsLayouter.NotifyItemRangeRemoved(source, startIndex, count);
}
}
- private void OnScrolling(object source, ScrollEventArgs args)
+ /// <summary>
+ /// Notify the observable item in startIndex is removed.
+ /// </summary>
+ /// <param name="source">Dataset source.</param>
+ /// <param name="startIndex">Index of removed item.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void NotifyItemRemoved(IItemSource source, int startIndex)
{
- layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
- List<RecycleItem> recycledItemList = layoutManager.Recycle(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
- BindData(recycledItemList);
+ if (InternalItemsLayouter != null)
+ {
+ InternalItemsLayouter.NotifyItemRemoved(source, startIndex);
+ }
}
- private void OnAdapterDataChanged(object source, EventArgs args)
+ /// <summary>
+ /// Realize indexed item.
+ /// </summary>
+ /// <param name="index"> Index position of realizing item </param>
+ internal virtual RecyclerViewItem RealizeItem(int index)
{
- List<RecycleItem> changedData = new List<RecycleItem>();
+ object context = InternalItemSource.GetItem(index);
+ // Check DataTemplate is Same!
+ if (ItemTemplate is DataTemplateSelector)
+ {
+ // Need to implements for caching of selector!
+ }
+ else
+ {
+ // pop item
+ RecyclerViewItem item = PopRecycleCache(ItemTemplate);
+ if (item != null)
+ {
+ DecorateItem(item, index, context);
+ return item;
+ }
+ }
- foreach (RecycleItem item in Children)
+ object content = DataTemplateExtensions.CreateContent(ItemTemplate, context, (BindableObject)this) ?? throw new Exception("Template return null object.");
+ if (content is RecyclerViewItem)
{
- changedData.Add(item);
+ RecyclerViewItem item = (RecyclerViewItem)content;
+ ContentContainer.Add(item);
+ DecorateItem(item, index, context);
+ return item;
+ }
+ else
+ {
+ throw new Exception("Template content must be type of ViewItem");
}
- BindData(changedData);
}
- private void BindData(List<RecycleItem> changedData)
+ /// <summary>
+ /// Unrealize indexed item.
+ /// </summary>
+ /// <param name="item"> Target item for unrealizing </param>
+ /// <param name="recycle"> Allow recycle. default is true </param>
+ internal virtual void UnrealizeItem(RecyclerViewItem item, bool recycle = true)
{
- foreach (RecycleItem item in changedData)
+ item.Index = -1;
+ item.ParentItemsView = null;
+ // Remove BindingContext null set for performance improving.
+ //item.BindingContext = null;
+ item.IsPressed = false;
+ item.IsSelected = false;
+ item.IsEnabled = true;
+ // Remove Update Style on default for performance improving.
+ //item.UpdateState();
+ item.Relayout -= OnItemRelayout;
+
+ if (!recycle || !PushRecycleCache(item))
{
- if (item.DataIndex > -1 && item.DataIndex < adapter.Data.Count)
- {
- item.Show();
- item.Name = "[" + item.DataIndex + "]";
- adapter.BindData(item);
- }
- else
- {
- item.Hide();
- }
+ //ContentContainer.Remove(item);
+ Utility.Dispose(item);
}
}
/// Adjust scrolling position by own scrolling rules.
/// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
/// </summary>
- /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
+ /// <param name="position">Scroll position which is calculated by ScrollableBase.</param>
/// <returns>Adjusted scroll destination</returns>
- /// <since_tizen> 8 </since_tizen>
- /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
[EditorBrowsable(EditorBrowsableState.Never)]
protected override float AdjustTargetPositionOfScrollAnimation(float position)
{
// Destination is depending on implementation of layout manager.
// Get destination from layout manager.
- return layoutManager.CalculateCandidateScrollPosition(position);
+ return InternalItemsLayouter.CalculateCandidateScrollPosition(position);
}
- private View focusedView;
- private int prevFocusedDataIndex = 0;
+ /// <summary>
+ /// Push the item into the recycle cache. this item will be reused in view update.
+ /// </summary>
+ /// <param name="item"> Target item to push into recycle cache. </param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual bool PushRecycleCache(RecyclerViewItem item)
+ {
+ if (item == null) throw new ArgumentNullException(nameof(item));
+ if (RecycleCache.Count >= CacheMax) return false;
+ if (item.Template == null) return false;
+ item.Hide();
+ item.Index = -1;
+ RecycleCache.Add(item);
+ return true;
+ }
- public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ /// <summary>
+ /// Pop the item from the recycle cache.
+ /// </summary>
+ /// <param name="Template"> Template of wanted item. </param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual RecyclerViewItem PopRecycleCache(DataTemplate Template)
{
- View nextFocusedView = null;
+ for (int i = 0; i < RecycleCache.Count; i++)
+ {
+ RecyclerViewItem item = RecycleCache[i];
+ if (item.Template == Template)
+ {
+ RecycleCache.Remove(item);
+ item.Show();
+ return item;
+ }
+ }
+ return null;
+ }
- if (!focusedView)
+ /// <summary>
+ /// On scroll event callback.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void OnScrolling(object source, ScrollEventArgs args)
+ {
+ if (args == null) throw new ArgumentNullException(nameof(args));
+ if (!disposed && InternalItemsLayouter != null && ItemsSource != null && ItemTemplate != null)
{
- // If focusedView is null, find child which has previous data index
- if (Children.Count > 0 && Adapter.Data.Count > 0)
- {
- for (int i = 0; i < Children.Count; i++)
- {
- RecycleItem item = Children[i] as RecycleItem;
- if (item.DataIndex == prevFocusedDataIndex)
- {
- nextFocusedView = item;
- break;
- }
- }
- }
+ //Console.WriteLine("[NUI] On Scrolling! {0} => {1}", ScrollPosition.Y, args.Position.Y);
+ InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
}
- else
+ }
+
+ /// <summary>
+ /// Dispose ItemsView and all children on it.
+ /// </summary>
+ /// <param name="type">Dispose type.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override void Dispose(DisposeTypes type)
+ {
+ if (disposed)
{
- // If this is not first focus, request next focus to LayoutManager
- if (LayoutManager != null)
- {
- nextFocusedView = LayoutManager.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
- }
+ return;
}
- if (nextFocusedView != null)
+ if (type == DisposeTypes.Explicit)
{
- // Check next focused view is inside of visible area.
- // If it is not, move scroll position to make it visible.
- Position scrollPosition = ContentContainer.CurrentPosition;
- float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
-
- float left = nextFocusedView.Position.X;
- float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
- float top = nextFocusedView.Position.Y;
- float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
-
- float visibleRectangleLeft = -scrollPosition.X;
- float visibleRectangleRight = -scrollPosition.X + Size.Width;
- float visibleRectangleTop = -scrollPosition.Y;
- float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
-
- if (ScrollingDirection == Direction.Horizontal)
- {
- if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
- {
- targetPosition = left;
- }
- else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
- {
- targetPosition = right - Size.Width;
- }
- }
- else
+ disposed = true;
+ // call the clear!
+ if (RecycleCache != null)
{
- if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
- {
- targetPosition = top;
- }
- else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
+ foreach (RecyclerViewItem item in RecycleCache)
{
- targetPosition = bottom - Size.Height;
+ //ContentContainer.Remove(item);
+ Utility.Dispose(item);
}
+ RecycleCache.Clear();
}
-
- focusedView = nextFocusedView;
- if ((nextFocusedView as RecycleItem) != null)
- {
- prevFocusedDataIndex = (nextFocusedView as RecycleItem).DataIndex;
- }
-
- ScrollTo(targetPosition, true);
+ InternalItemsLayouter.Clear();
+ InternalItemsLayouter = null;
+ ItemsSource = null;
+ ItemTemplate = null;
+ if (InternalItemSource != null) InternalItemSource.Dispose();
+ //
}
- else
- {
- // If nextView is null, it means that we should move focus to outside of Control.
- // Return FocusableView depending on direction.
- switch (direction)
- {
- case View.FocusDirection.Left:
- {
- nextFocusedView = LeftFocusableView;
- break;
- }
- case View.FocusDirection.Right:
- {
- nextFocusedView = RightFocusableView;
- break;
- }
- case View.FocusDirection.Up:
- {
- nextFocusedView = UpFocusableView;
- break;
- }
- case View.FocusDirection.Down:
- {
- nextFocusedView = DownFocusableView;
- break;
- }
- }
- if (nextFocusedView)
- {
- focusedView = null;
- }
- else
- {
- //If FocusableView doesn't exist, not move focus.
- nextFocusedView = focusedView;
- }
- }
+ base.Dispose(type);
+ }
- return nextFocusedView;
+ private void OnItemRelayout(object sender, EventArgs e)
+ {
+ //FIXME: we need to skip the first relayout and only call size changed when real size change happen.
+ //InternalItemsLayouter.NotifyItemSizeChanged((sender as ViewItem));
+ //InternalItemsLayouter.RequestLayout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
+ }
+
+ private void DecorateItem(RecyclerViewItem item, int index, object context)
+ {
+ item.Index = index;
+ item.ParentItemsView = this;
+ item.Template = (ItemTemplate as DataTemplateSelector)?.SelectDataTemplate(InternalItemSource.GetItem(index), this) ?? ItemTemplate;
+ item.BindingContext = context;
+ item.Relayout += OnItemRelayout;
}
}
}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// Selection changed event. this might be deprecated.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class SelectionChangedEventArgs : EventArgs
+ {
+ static readonly IReadOnlyList<object> selectEmpty = new List<object>(0);
+
+ /// <summary>
+ /// Previous selection list.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public IReadOnlyList<object> PreviousSelection { get; }
+
+ /// <summary>
+ /// Current selection list.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public IReadOnlyList<object> CurrentSelection { get; }
+
+
+ internal SelectionChangedEventArgs(object previousSelection, object currentSelection)
+ {
+ PreviousSelection = previousSelection != null ? new List<object>(1) { previousSelection } : selectEmpty;
+ CurrentSelection = currentSelection != null ? new List<object>(1) { currentSelection } : selectEmpty;
+ }
+
+ internal SelectionChangedEventArgs(IList<object> previousSelection, IList<object> currentSelection)
+ {
+ PreviousSelection = new List<object>(previousSelection ?? throw new ArgumentNullException(nameof(previousSelection)));
+ CurrentSelection = new List<object>(currentSelection ?? throw new ArgumentNullException(nameof(currentSelection)));
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace Tizen.NUI.Components
+{
+ // Used by the CollectionView to keep track of (and respond to changes in) the SelectedItems property
+ internal class SelectionList : IList<object>
+ {
+ static readonly IList<object> selectEmpty = new List<object>(0);
+ readonly CollectionView ColView;
+ readonly IList<object> internalList;
+ IList<object> shadowList;
+ bool externalChange;
+
+ public SelectionList(CollectionView colView, IList<object> items = null)
+ {
+ ColView = colView ?? throw new ArgumentNullException(nameof(colView));
+ internalList = items ?? new List<object>();
+ shadowList = Copy();
+
+ if (items is INotifyCollectionChanged incc)
+ {
+ incc.CollectionChanged += OnCollectionChanged;
+ }
+ }
+
+ public object this[int index] { get => internalList[index]; set => internalList[index] = value; }
+
+ public int Count => internalList.Count;
+
+ public bool IsReadOnly => false;
+
+ public void Add(object item)
+ {
+ externalChange = true;
+ internalList.Add(item);
+ externalChange = false;
+
+ ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+ shadowList.Add(item);
+ }
+
+ public void Clear()
+ {
+ externalChange = true;
+ internalList.Clear();
+ externalChange = false;
+
+ ColView.SelectedItemsPropertyChanged(shadowList, selectEmpty);
+ shadowList.Clear();
+ }
+
+ public bool Contains(object item)
+ {
+ return internalList.Contains(item);
+ }
+
+ public void CopyTo(object[] array, int arrayIndex)
+ {
+ internalList.CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator<object> GetEnumerator()
+ {
+ return internalList.GetEnumerator();
+ }
+
+ public int IndexOf(object item)
+ {
+ return internalList.IndexOf(item);
+ }
+
+ public void Insert(int index, object item)
+ {
+ externalChange = true;
+ internalList.Insert(index, item);
+ externalChange = false;
+
+ ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+ shadowList.Insert(index, item);
+ }
+
+ public bool Remove(object item)
+ {
+ externalChange = true;
+ var removed = internalList.Remove(item);
+ externalChange = false;
+
+ if (removed)
+ {
+ ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+ shadowList.Remove(item);
+ }
+
+ return removed;
+ }
+
+ public void RemoveAt(int index)
+ {
+ externalChange = true;
+ internalList.RemoveAt(index);
+ externalChange = false;
+
+ ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+ shadowList.RemoveAt(index);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return internalList.GetEnumerator();
+ }
+
+ List<object> Copy()
+ {
+ var items = new List<object>();
+ for (int n = 0; n < internalList.Count; n++)
+ {
+ items.Add(internalList[n]);
+ }
+
+ return items;
+ }
+
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ if (externalChange)
+ {
+ // If this change was initiated by a renderer or direct manipulation of ColllectionView.SelectedItems,
+ // we don't need to send a selection change notification
+ return;
+ }
+
+ // This change is coming from a bound viewmodel property
+ // Emit a selection change notification, then bring the shadow copy up-to-date
+ ColView.SelectedItemsPropertyChanged(shadowList, internalList);
+ shadowList = Copy();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// DefaultGridItemStyle is a class which saves DefaultLinearItem's ux data.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class DefaultGridItemStyle : RecyclerViewItemStyle
+ {
+
+ static DefaultGridItemStyle() { }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultGridItemStyle.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ public DefaultGridItemStyle() : base()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultGridItemStyle with style.
+ /// </summary>
+ /// <param name="style">Create DefaultGridItemStyle by style customized by user.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultGridItemStyle(DefaultGridItemStyle style) : base(style)
+ {
+ }
+
+
+ /// <summary>
+ /// Label Text's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TextLabelStyle Caption { get; set; } = new TextLabelStyle();
+
+ /// <summary>
+ /// Icon's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ImageViewStyle Image { get; set; } = new ImageViewStyle();
+
+ /// <summary>
+ /// Extra's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ViewStyle Badge { get; set; } = new ViewStyle();
+
+ /// <summary>
+ /// Style's clone function.
+ /// </summary>
+ /// <param name="bindableObject">The style that need to copy.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void CopyFrom(BindableObject bindableObject)
+ {
+ base.CopyFrom(bindableObject);
+
+ if (bindableObject is DefaultGridItemStyle RecyclerViewItemStyle)
+ {
+ Caption.CopyFrom(RecyclerViewItemStyle.Caption);
+ Image.CopyFrom(RecyclerViewItemStyle.Image);
+ Badge.CopyFrom(RecyclerViewItemStyle.Badge);
+ //Border.CopyFrom(RecyclerViewItemStyle.Border);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// DefaultLinearItemStyle is a class which saves DefaultLinearItem's ux data.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class DefaultLinearItemStyle : RecyclerViewItemStyle
+ {
+ static DefaultLinearItemStyle() { }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultLinearItemStyle.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ public DefaultLinearItemStyle() : base()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultLinearItemStyle with style.
+ /// </summary>
+ /// <param name="style">Create DefaultLinearItemStyle by style customized by user.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultLinearItemStyle(DefaultLinearItemStyle style) : base(style)
+ {
+ }
+
+ /// <summary>
+ /// Label Text's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TextLabelStyle Label { get; set; } = new TextLabelStyle();
+
+ /// <summary>
+ /// Sublabel Text's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TextLabelStyle SubLabel { get; set; } = new TextLabelStyle();
+
+ /// <summary>
+ /// Icon's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ViewStyle Icon { get; set; } = new ViewStyle();
+
+ /// <summary>
+ /// Extra's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ViewStyle Extra { get; set; } = new ViewStyle();
+
+ /// <summary>
+ /// Seperator's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ViewStyle Seperator { get; set; } = new ViewStyle();
+
+ /// <summary>
+ /// Style's clone function.
+ /// </summary>
+ /// <param name="bindableObject">The style that need to copy.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void CopyFrom(BindableObject bindableObject)
+ {
+ base.CopyFrom(bindableObject);
+
+ if (bindableObject is DefaultLinearItemStyle RecyclerViewItemStyle)
+ {
+ Label.CopyFrom(RecyclerViewItemStyle.Label);
+ SubLabel.CopyFrom(RecyclerViewItemStyle.SubLabel);
+ Icon.CopyFrom(RecyclerViewItemStyle.Icon);
+ Extra.CopyFrom(RecyclerViewItemStyle.Extra);
+ Seperator.CopyFrom(RecyclerViewItemStyle.Seperator);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// DefaultTitleItemStyle is a class which saves DefaultLinearItem's ux data.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class DefaultTitleItemStyle : RecyclerViewItemStyle
+ {
+ static DefaultTitleItemStyle() { }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultTitleItemStyle.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ public DefaultTitleItemStyle() : base()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new instance of a DefaultTitleItemStyle with style.
+ /// </summary>
+ /// <param name="style">Create DefaultTitleItemStyle by style customized by user.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DefaultTitleItemStyle(DefaultTitleItemStyle style) : base(style)
+ {
+ }
+
+ /// <summary>
+ /// Label Text's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TextLabelStyle Label { get; set; } = new TextLabelStyle();
+
+ /// <summary>
+ /// Icon's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ViewStyle Icon { get; set; } = new ViewStyle();
+
+ /// <summary>
+ /// Seperator's style.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ViewStyle Seperator { get; set; } = new ViewStyle();
+
+ /// <summary>
+ /// Style's clone function.
+ /// </summary>
+ /// <param name="bindableObject">The style that need to copy.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void CopyFrom(BindableObject bindableObject)
+ {
+ base.CopyFrom(bindableObject);
+
+ if (bindableObject is DefaultTitleItemStyle RecyclerViewItemStyle)
+ {
+ Label.CopyFrom(RecyclerViewItemStyle.Label);
+ Icon.CopyFrom(RecyclerViewItemStyle.Icon);
+ Seperator.CopyFrom(RecyclerViewItemStyle.Seperator);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System.ComponentModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+using Tizen.NUI.Components.Extension;
+
+namespace Tizen.NUI.Components
+{
+ /// <summary>
+ /// RecyclerViewItemStyle is a class which saves RecyclerViewItem's ux data.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RecyclerViewItemStyle : ControlStyle
+ {
+ /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty IsSelectableProperty = BindableProperty.Create(nameof(IsSelectable), typeof(bool?), typeof(RecyclerViewItemStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+ RecyclerViewItemStyle.isSelectable = (bool?)newValue;
+ },
+ defaultValueCreator: (bindable) =>
+ {
+ var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+ return RecyclerViewItemStyle.isSelectable;
+ });
+ /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool?), typeof(RecyclerViewItemStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+ RecyclerViewItemStyle.isSelected = (bool?)newValue;
+ },
+ defaultValueCreator: (bindable) =>
+ {
+ var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+ return RecyclerViewItemStyle.isSelected;
+ });
+ /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(nameof(IsEnabled), typeof(bool?), typeof(RecyclerViewItemStyle), null, propertyChanged: (bindable, oldValue, newValue) =>
+ {
+ var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+ RecyclerViewItemStyle.isEnabled = (bool?)newValue;
+ },
+ defaultValueCreator: (bindable) =>
+ {
+ var RecyclerViewItemStyle = (RecyclerViewItemStyle)bindable;
+ return RecyclerViewItemStyle.isEnabled;
+ });
+
+ private bool? isSelectable;
+ private bool? isSelected;
+ private bool? isEnabled;
+
+ static RecyclerViewItemStyle() { }
+
+ /// <summary>
+ /// Creates a new instance of a RecyclerViewItemStyle.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ public RecyclerViewItemStyle() : base()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new instance of a RecyclerViewItemStyle with style.
+ /// </summary>
+ /// <param name="style">Create RecyclerViewItemStyle by style customized by user.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecyclerViewItemStyle(RecyclerViewItemStyle style) : base(style)
+ {
+ }
+
+ /// <summary>
+ /// Flag to decide RecyclerViewItem can be selected or not.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool? IsSelectable
+ {
+ get => (bool?)GetValue(IsSelectableProperty);
+ set => SetValue(IsSelectableProperty, value);
+ }
+
+ /// <summary>
+ /// Flag to decide selected state in RecyclerViewItem.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool? IsSelected
+ {
+ get => (bool?)GetValue(IsSelectedProperty);
+ set => SetValue(IsSelectedProperty, value);
+ }
+
+ /// <summary>
+ /// Flag to decide RecyclerViewItem can be selected or not.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool? IsEnabled
+ {
+ get => (bool?)GetValue(IsEnabledProperty);
+ set => SetValue(IsEnabledProperty, value);
+ }
+
+ /// <summary>
+ /// Style's clone function.
+ /// </summary>
+ /// <param name="bindableObject">The style that need to copy.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override void CopyFrom(BindableObject bindableObject)
+ {
+ base.CopyFrom(bindableObject);
+
+ /*
+ if (bindableObject is RecyclerViewItemStyle RecyclerViewItemStyle)
+ {
+ //
+ }
+ */
+ }
+ }
+}
["PaginationIndicatorImageUrlSelected"] = FrameworkInformation.ResourcePath + "nui_component_default_pagination_focus_dot.png",
["ScrollbarTrackColor"] = new Color(1, 1, 1, 0.15f),
["ScrollbarThumbColor"] = new Color(0.6f, 0.6f, 0.6f, 1.0f),
+ ["RecyclerViewItemBackgroundColorNormal"] = new Color(1, 1, 1, 1),
+ ["RecyclerViewItemBackgroundColorPressed"] = new Color(0.85f, 0.85f, 0.85f, 1),
+ ["RecyclerViewItemBackgroundColorDisabled"] = new Color(0.70f, 0.70f, 0.70f, 1),
+ ["RecyclerViewItemBackgroundColorSelected"] = new Color(0.701f, 0.898f, 0.937f, 1),
+ ["TitleBackgroundColorNormal"] = new Color(0.78f, 0.78f, 0.78f, 1),
};
public Theme Create() => Create(null);
TrackPadding = 4
});
+ theme.AddStyleWithoutClone("Tizen.NUI.Components.RecyclerViewItem", new RecyclerViewItemStyle()
+ {
+ BackgroundColor = new Selector<Color>()
+ {
+ Normal = (Color)theme.Resources["RecyclerViewItemBackgroundColorNormal"],
+ Pressed = (Color)theme.Resources["RecyclerViewItemBackgroundColorPressed"],
+ Disabled = (Color)theme.Resources["RecyclerViewItemBackgroundColorDisabled"],
+ Selected = (Color)theme.Resources["RecyclerViewItemBackgroundColorSelected"],
+ },
+ });
+
+ theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultLinearItem", new DefaultLinearItemStyle()
+ {
+ SizeHeight = 130,
+ Padding = new Extents(20, 20, 5, 5),
+ BackgroundColor = new Selector<Color>()
+ {
+ Normal = (Color)theme.Resources["RecyclerViewItemBackgroundColorNormal"],
+ Pressed = (Color)theme.Resources["RecyclerViewItemBackgroundColorPressed"],
+ Disabled = (Color)theme.Resources["RecyclerViewItemBackgroundColorDisabled"],
+ Selected = (Color)theme.Resources["RecyclerViewItemBackgroundColorSelected"],
+ },
+ Label = new TextLabelStyle()
+ {
+ PointSize = 10,
+ Ellipsis = true,
+ },
+ SubLabel = new TextLabelStyle()
+ {
+ PointSize = 6,
+ Ellipsis = true,
+ },
+ Icon = new ViewStyle()
+ {
+ Margin = new Extents(0, 20, 0, 0)
+ },
+ Extra = new ViewStyle()
+ {
+ Margin = new Extents(20, 0, 0, 0)
+ },
+ Seperator = new ViewStyle()
+ {
+ Margin = new Extents(5, 5, 0, 0),
+ BackgroundColor = new Color(0.78f, 0.78f, 0.78f, 1),
+ },
+ });
+ theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultGridItem", new DefaultGridItemStyle()
+ {
+ Padding = new Extents(5, 5, 5, 5),
+ Caption = new TextLabelStyle()
+ {
+ PointSize = 9,
+ Ellipsis = true,
+ },
+ Badge = new ViewStyle()
+ {
+ Margin = new Extents(5, 5, 5, 5),
+ },
+ });
+
+ theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultTitleItem", new DefaultTitleItemStyle()
+ {
+ SizeHeight = 90,
+ Padding = new Extents(10, 10, 5, 5),
+ BackgroundColor = new Selector<Color>()
+ {
+ Normal = (Color)theme.Resources["TitleBackgroundColorNormal"],
+ },
+ Label = new TextLabelStyle()
+ {
+ PointSize = 10,
+ Ellipsis = true,
+ },
+ Icon = new ViewStyle()
+ {
+ Margin = new Extents(10, 0, 0, 0)
+ },
+ Seperator = new ViewStyle()
+ {
+ Margin = new Extents(0, 0, 0, 0),
+ BackgroundColor = new Color(0.85f, 0.85f, 0.85f, 1),
+ },
+ });
+
return theme;
}
}
["PaginationIndicatorImageUrlSelected"] = FrameworkInformation.ResourcePath + "nui_component_default_pagination_focus_dot.png",
["ScrollbarTrackColor"] = new Color(1, 1, 1, 0.15f),
["ScrollbarThumbColor"] = new Color(0.6f, 0.6f, 0.6f, 1.0f),
+ ["RecyclerViewItemBackgroundColorNormal"] = new Color(1, 1, 1, 1),
+ ["RecyclerViewItemBackgroundColorPressed"] = new Color(0.85f, 0.85f, 0.85f, 1),
+ ["RecyclerViewItemBackgroundColorDisabled"] = new Color(0.70f, 0.70f, 0.70f, 1),
+ ["RecyclerViewItemBackgroundColorSelected"] = new Color(0.701f, 0.898f, 0.937f, 1),
+ ["TitleBackgroundColorNormal"] = new Color(0.78f, 0.78f, 0.78f, 1),
};
public Theme Create() => Create(null);
TrackPadding = 4
});
+ theme.AddStyleWithoutClone("Tizen.NUI.Components.RecyclerViewItem", new RecyclerViewItemStyle()
+ {
+ BackgroundColor = new Selector<Color>()
+ {
+ Normal = (Color)theme.Resources["RecyclerViewItemBackgroundColorNormal"],
+ Pressed = (Color)theme.Resources["RecyclerViewItemBackgroundColorPressed"],
+ Disabled = (Color)theme.Resources["RecyclerViewItemBackgroundColorDisabled"],
+ Selected = (Color)theme.Resources["RecyclerViewItemBackgroundColorSelected"],
+ },
+ });
+
+ theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultLinearItem", new DefaultLinearItemStyle()
+ {
+ SizeHeight = 160,
+ Padding = new Extents(10, 10, 20, 20),
+ BackgroundColor = new Selector<Color>()
+ {
+ Normal = (Color)theme.Resources["RecyclerViewItemBackgroundColorNormal"],
+ Pressed = (Color)theme.Resources["RecyclerViewItemBackgroundColorPressed"],
+ Disabled = (Color)theme.Resources["RecyclerViewItemBackgroundColorDisabled"],
+ Selected = (Color)theme.Resources["RecyclerViewItemBackgroundColorSelected"],
+ },
+ Label = new TextLabelStyle()
+ {
+ PointSize = 20,
+ Ellipsis = true,
+ },
+ SubLabel = new TextLabelStyle()
+ {
+ PointSize = 12,
+ Ellipsis = true,
+ },
+ Icon = new ViewStyle()
+ {
+ Margin = new Extents(0, 10, 0, 0)
+ },
+ Extra = new ViewStyle()
+ {
+ Margin = new Extents(10, 0, 0, 0)
+ },
+ Seperator = new ViewStyle()
+ {
+ Margin = new Extents(5, 5, 0, 0),
+ BackgroundColor = new Color(0.78f, 0.78f, 0.78f, 1),
+ },
+ });
+ theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultGridItem", new DefaultGridItemStyle()
+ {
+ Padding = new Extents(5, 5, 5, 5),
+ Caption = new TextLabelStyle()
+ {
+ PointSize = 9,
+ Ellipsis = true,
+ },
+ Badge = new ViewStyle()
+ {
+ Margin = new Extents(5, 5, 5, 5),
+ },
+ });
+
+ theme.AddStyleWithoutClone("Tizen.NUI.Components.DefaultTitleItem", new DefaultTitleItemStyle()
+ {
+ SizeHeight = 50,
+ Padding = new Extents(10, 10, 5, 5),
+ BackgroundColor = new Selector<Color>()
+ {
+ Normal = (Color)theme.Resources["TitleBackgroundColorNormal"],
+ },
+ Label = new TextLabelStyle()
+ {
+ PointSize = 15,
+ Ellipsis = true,
+ },
+ Icon = new ViewStyle()
+ {
+ Margin = new Extents(10, 0, 0, 0)
+ },
+ Seperator = new ViewStyle()
+ {
+ Margin = new Extents(0, 0, 0, 0),
+ BackgroundColor = new Color(0.85f, 0.85f, 0.85f, 1),
+ },
+ });
+
return theme;
}
}
--- /dev/null
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Wearable
+{
+ /// <summary>
+ /// [Draft] This class implements a grid box layout.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class GridRecycleLayoutManager : RecycleLayoutManager
+ {
+ private int rows = 1;
+
+ /// <summary>
+ /// [draft ]Get/Set the number of rows in the grid
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int Rows
+ {
+ get
+ {
+ return rows;
+ }
+ set
+ {
+ rows = value;
+
+ if (Container != null)
+ {
+ Layout(PrevScrollPosition);
+ }
+ }
+ }
+
+ private int columns = 1;
+
+
+ /// <summary>
+ /// [Draft] Get/Set the number of columns in the grid
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int Columns
+ {
+ get
+ {
+ return columns;
+ }
+ set
+ {
+ columns = value;
+
+ if (Container != null)
+ {
+ Layout(PrevScrollPosition);
+ }
+ }
+ }
+
+ private int firstVisibleItemIndex = -1;
+ private int lastVisibleItemIndex = -1;
+
+ private bool IsItemVisible(float scrollPosition, RecycleItem item)
+ {
+ bool result = false;
+ View list = Container.GetParent() as View;
+ if (list == null)
+ {
+ return result;
+ }
+ Vector2 visibleArea = new Vector2(Math.Abs(scrollPosition),
+ Math.Abs(scrollPosition) + (LayoutOrientation == Orientation.Vertical ?
+ list.Size.Width : list.Size.Height)
+ );
+
+ float firstCheckPoint = LayoutOrientation == Orientation.Vertical ? item.Position.X : item.Position.Y;
+ float secondCheckPoint = LayoutOrientation == Orientation.Vertical ?
+ firstCheckPoint + item.Size.Width :
+ firstCheckPoint + item.Size.Height;
+
+ result = (firstCheckPoint >= visibleArea.X && firstCheckPoint <= visibleArea.Y) || (secondCheckPoint >= visibleArea.X && secondCheckPoint <= visibleArea.Y);
+
+ return result;
+ }
+
+ /// <summary>
+ /// This is called to find out how much container size can be.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override float CalculateLayoutOrientationSize()
+ {
+ float orientationFactor = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
+ return StepSize * (int)Math.Ceiling((double)DataCount / (double)orientationFactor);
+ }
+
+
+ /// <summary>
+ /// This is called to find out where items are lain out according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ public override void Layout(float scrollPosition)
+ {
+ int itemInGroup = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
+ firstVisibleItemIndex = -1;
+ lastVisibleItemIndex = -1;
+
+ RecycleItem previousItem = null;
+
+ for (int i = 0; i < Container.Children.Count; i++)
+ {
+ RecycleItem item = Container.Children[i] as RecycleItem;
+
+ if (previousItem != null && item != null)
+ {
+ item.Position = LayoutOrientation == Orientation.Vertical ?
+ new Position(
+ (i % itemInGroup == 0 ?
+ previousItem.Position.X + (previousItem.CurrentSize.Width != 0 ?
+ previousItem.CurrentSize.Width :
+ previousItem.Size.Width) :
+ previousItem.Position.X),
+ (i % itemInGroup == 0 ?
+ 0 :
+ previousItem.PositionY + (previousItem.CurrentSize.Height != 0 ?
+ previousItem.CurrentSize.Height :
+ previousItem.Size.Height))
+ ) :
+ new Position(
+ (i % itemInGroup == 0 ?
+ 0 :
+ previousItem.PositionX + (previousItem.CurrentSize.Width != 0 ?
+ previousItem.CurrentSize.Width :
+ previousItem.Size.Width)),
+ (i % itemInGroup == 0 ?
+ previousItem.Position.Y + (previousItem.CurrentSize.Height != 0 ?
+ previousItem.CurrentSize.Height :
+ previousItem.Size.Height) :
+ previousItem.Position.Y)
+ );
+ }
+
+ bool isVisible = IsItemVisible(scrollPosition, item);
+
+ if (isVisible)
+ {
+ firstVisibleItemIndex = firstVisibleItemIndex == -1 ? i : firstVisibleItemIndex;
+ lastVisibleItemIndex = i;
+ }
+
+ previousItem = item;
+ }
+
+ if (StepSize == 0)
+ {
+ StepSize = LayoutOrientation == Orientation.Vertical ? ItemSize.Width : ItemSize.Height;
+ }
+ }
+
+
+ /// <summary>
+ /// This is called to find out which items should be recycled according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <returns>List of RecycleItems which should be recycled.</returns>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ public override List<RecycleItem> Recycle(float scrollPosition)
+ {
+ List<RecycleItem> result = new List<RecycleItem>();
+ bool checkFront = (PrevScrollPosition - scrollPosition) > 0;
+
+ int itemInGroup = LayoutOrientation == Orientation.Vertical ? Rows : Columns;
+
+ if (checkFront)
+ {
+ int currentGroupNum = (int)(firstVisibleItemIndex / itemInGroup) + 1;
+
+ if (currentGroupNum > 2)
+ {
+ // Too many item is in front!!! move first item to back!!!!
+ for (int i = 0; i < itemInGroup; i++)
+ {
+ RecycleItem target = Container.Children[0] as RecycleItem;
+ if (target != null)
+ {
+ target.DataIndex = target.DataIndex + Container.Children.Count;
+ target.SiblingOrder = Container.Children.Count - 1;
+
+ result.Add(target);
+ }
+ }
+ }
+ }
+ else
+ {
+ int currentGroupNum = (int)(lastVisibleItemIndex / itemInGroup) + 1;
+
+ if (currentGroupNum < (int)(Container.Children.Count / itemInGroup) - 3)
+ {
+ for (int i = 0; i < itemInGroup; i++)
+ {
+ RecycleItem prevFirstItem = Container.Children[itemInGroup] as RecycleItem;
+ RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem;
+ if (prevFirstItem != null && target != null)
+ {
+ target.Position = new Position(
+ LayoutOrientation == Orientation.Vertical ? (prevFirstItem.Position.X - target.Size.Width) : prevFirstItem.Position.X,
+ LayoutOrientation == Orientation.Vertical ? prevFirstItem.Position.Y : (prevFirstItem.Position.Y - target.Size.Height)
+ );
+ target.DataIndex = target.DataIndex - Container.Children.Count;
+ target.SiblingOrder = 0;
+
+ result.Add(target);
+ }
+ }
+ }
+ }
+
+
+ PrevScrollPosition = scrollPosition;
+
+ return result;
+ }
+
+ /// <summary>
+ /// Adjust scrolling position by own scrolling rules.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ public override float CalculateCandidateScrollPosition(float scrollPosition)
+ {
+ return scrollPosition;
+ }
+
+ public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ {
+ View nextFocusedView = null;
+ int targetSibling = -1;
+ bool isHorizontal = LayoutOrientation == Orientation.Horizontal;
+
+ switch (direction)
+ {
+ case View.FocusDirection.Left:
+ {
+ targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - 1 : currentFocusedView.SiblingOrder - Rows;
+ break;
+ }
+ case View.FocusDirection.Right:
+ {
+ targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + 1 : currentFocusedView.SiblingOrder + Rows;
+ break;
+ }
+ case View.FocusDirection.Up:
+ {
+ targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - Columns : currentFocusedView.SiblingOrder - 1;
+ break;
+ }
+ case View.FocusDirection.Down:
+ {
+ targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + Columns : currentFocusedView.SiblingOrder + 1;
+ break;
+ }
+ }
+
+ if (targetSibling > -1 && targetSibling < Container.Children.Count)
+ {
+ RecycleItem candidate = Container.Children[targetSibling] as RecycleItem;
+ if (candidate != null && candidate.DataIndex >= 0 && candidate.DataIndex < DataCount)
+ {
+ nextFocusedView = candidate;
+ }
+ }
+
+ return nextFocusedView;
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Wearable
+{
+ /// <summary>
+ /// [Draft] This class implements a linear box layout.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class LinearRecycleLayoutManager : RecycleLayoutManager
+ {
+ private int firstVisibleItemIndex = -1;
+ private int lastVisibleItemIndex = -1;
+
+ private bool IsItemVisible(float scrollPosition, RecycleItem item)
+ {
+ bool result = false;
+ View list = Container.GetParent() as View;
+ if (list == null)
+ {
+ return result;
+ }
+
+ Vector2 visibleArea = new Vector2(Math.Abs(scrollPosition),
+ Math.Abs(scrollPosition) + (LayoutOrientation == Orientation.Horizontal ?
+ list.Size.Width : list.Size.Height)
+ );
+
+ float firstCheckPoint = LayoutOrientation == Orientation.Horizontal ? item.Position.X : item.Position.Y;
+ float secondCheckPoint = LayoutOrientation == Orientation.Horizontal ?
+ firstCheckPoint + item.Size.Width :
+ firstCheckPoint + item.Size.Height;
+
+ // Tizen.Log.Error("NUI", "[1p] "+visibleArea.X+ " =< "+firstCheckPoint+" =< "+visibleArea.Y+" ==== \n");
+ // Tizen.Log.Error("NUI", "[2p] "+visibleArea.X+ " =< "+secondCheckPoint+" =< "+visibleArea.Y+" ==== \n");
+
+ result = (firstCheckPoint >= visibleArea.X && firstCheckPoint <= visibleArea.Y) || (secondCheckPoint >= visibleArea.X && secondCheckPoint <= visibleArea.Y);
+
+ return result;
+ }
+
+ /// <summary>
+ /// This is called to find out where items are lain out according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ public override void Layout(float scrollPosition)
+ {
+ firstVisibleItemIndex = -1;
+ lastVisibleItemIndex = -1;
+
+ RecycleItem previousItem = null;
+
+ for (int i = 0; i < Container.Children.Count; i++)
+ {
+ RecycleItem item = Container.Children[i] as RecycleItem;
+
+ if (previousItem != null && item != null)
+ {
+ item.Position = LayoutOrientation == Orientation.Horizontal ?
+ new Position(
+ previousItem.Position.X + (previousItem.CurrentSize.Width != 0 ?
+ previousItem.CurrentSize.Width :
+ previousItem.Size.Width),
+ item.PositionY
+ ) :
+ new Position(
+ item.PositionX,
+ previousItem.Position.Y + (previousItem.CurrentSize.Height != 0 ?
+ previousItem.CurrentSize.Height :
+ previousItem.Size.Height)
+ );
+ }
+
+ bool isVisible = IsItemVisible(scrollPosition, item);
+
+ if (isVisible)
+ {
+ firstVisibleItemIndex = firstVisibleItemIndex == -1 ? i : firstVisibleItemIndex;
+ lastVisibleItemIndex = i;
+ }
+
+ previousItem = item;
+
+ // Tizen.Log.Error("NUI","["+item.DataIndex+"] "+item.Position.Y+" ==== \n");
+ }
+
+ if (StepSize == 0)
+ {
+ StepSize = LayoutOrientation == Orientation.Horizontal ? ItemSize.Width : ItemSize.Height;
+ }
+ }
+
+ /// <summary>
+ /// This is called to find out how much container size can be.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override float CalculateLayoutOrientationSize()
+ {
+ return StepSize * DataCount;
+ }
+
+ /// <summary>
+ /// This is called to find out which items should be recycled according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <returns>List of RecycleItems which should be recycled.</returns>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ public override List<RecycleItem> Recycle(float scrollPosition)
+ {
+ List<RecycleItem> result = new List<RecycleItem>();
+
+ bool checkFront = (PrevScrollPosition - scrollPosition) > 0;
+
+ if (checkFront)
+ {
+ if (firstVisibleItemIndex > 3)
+ {
+ // Too many item is in front!!! move first item to back!!!!
+ RecycleItem target = Container.Children[0] as RecycleItem;
+ if (target != null)
+ {
+ target.DataIndex = target.DataIndex + Container.Children.Count;
+ target.SiblingOrder = Container.Children.Count - 1;
+
+ result.Add(target);
+ }
+ }
+ }
+ else
+ {
+ if (lastVisibleItemIndex < Container.Children.Count - 3)
+ {
+ RecycleItem prevFirstItem = Container.Children[0] as RecycleItem;
+ RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem;
+ if (prevFirstItem != null && target != null)
+ {
+ target.Position = new Position(
+ LayoutOrientation == Orientation.Horizontal ? (prevFirstItem.Position.X - target.Size.Width) : prevFirstItem.Position.X,
+ LayoutOrientation == Orientation.Horizontal ? prevFirstItem.Position.Y : (prevFirstItem.Position.Y - target.Size.Height)
+ );
+ target.DataIndex = target.DataIndex - Container.Children.Count;
+ target.SiblingOrder = 0;
+
+ result.Add(target);
+ }
+ }
+ }
+
+ PrevScrollPosition = scrollPosition;
+
+ return result;
+ }
+
+ /// <summary>
+ /// Adjust scrolling position by own scrolling rules.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ public override float CalculateCandidateScrollPosition(float scrollPosition)
+ {
+ return scrollPosition;
+ }
+
+ public override View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ {
+ View nextFocusedView = null;
+ int targetSibling = -1;
+ bool isHorizontal = LayoutOrientation == Orientation.Horizontal;
+
+ switch (direction)
+ {
+ case View.FocusDirection.Left:
+ {
+ targetSibling = isHorizontal ? currentFocusedView.SiblingOrder - 1 : targetSibling;
+ break;
+ }
+ case View.FocusDirection.Right:
+ {
+ targetSibling = isHorizontal ? currentFocusedView.SiblingOrder + 1 : targetSibling;
+ break;
+ }
+ case View.FocusDirection.Up:
+ {
+ targetSibling = isHorizontal ? targetSibling : currentFocusedView.SiblingOrder - 1;
+ break;
+ }
+ case View.FocusDirection.Down:
+ {
+ targetSibling = isHorizontal ? targetSibling : currentFocusedView.SiblingOrder + 1;
+ break;
+ }
+ }
+
+ if (targetSibling > -1 && targetSibling < Container.Children.Count)
+ {
+ RecycleItem candidate = Container.Children[targetSibling] as RecycleItem;
+ if (candidate != null && candidate.DataIndex >= 0 && candidate.DataIndex < DataCount)
+ {
+ nextFocusedView = candidate;
+ }
+ }
+
+ return nextFocusedView;
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.Components;
+
+namespace Tizen.NUI.Wearable
+{
+ /// <summary>
+ /// [Draft] Defalt adapter for RecyclerView.
+ /// Managing RecycleItem and Data for RecyclerView.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RecycleAdapter
+ {
+ private List<object> mData = new List<object>();
+
+ /// <summary>
+ /// Create recycle item for RecyclerView.
+ /// RecyclerView will make its children using this api.
+ /// </summary>
+ /// <returns>Item for RecyclerView</returns>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual RecycleItem CreateRecycleItem()
+ {
+ return new RecycleItem();
+ }
+
+ /// <summary>
+ /// Bind data with recycler item.
+ /// This function is called when RecyclerItem is used again with new data.
+ /// Can update content of recycle item with new data at DataIndex of item.
+ /// </summary>
+ /// <param name="item">Reused RecycleItem which needs data binding.</param>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void BindData(RecycleItem item)
+ {
+
+ }
+
+ /// <summary>
+ /// Notify when data of adapter is changed.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void Notify()
+ {
+ OnDataChanged?.Invoke(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Triggered when user called Notify().
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event EventHandler<EventArgs> OnDataChanged;
+
+ /// <summary>
+ /// Triggered when user called Notify().
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public List<object> Data
+ {
+ get
+ {
+ return mData;
+ }
+ set
+ {
+ mData = value;
+ Notify();
+ }
+ }
+ }
+}
--- /dev/null
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System.ComponentModel;
+using Tizen.NUI.Components;
+
+namespace Tizen.NUI.Wearable
+{
+ /// <summary>
+ /// [Draft] This class provides a basic item for RecyclerView.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RecycleItem : Control
+ {
+ /// <summary>
+ /// Data index which is binded to item by RecycleAdapter.
+ /// Can access to data of RecycleAdapter using this index.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int DataIndex { get; set; } = 0;
+ }
+}
--- /dev/null
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Wearable
+{
+ /// <summary>
+ /// [Draft] Defalt layout manager for RecyclerView.
+ /// Lay out RecycleItem and recycle RecycleItem.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RecycleLayoutManager
+ {
+ /// <summary>
+ /// Enumeration for the direction in which the content is laid out
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum Orientation
+ {
+ /// <summary>
+ /// Vertical
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Vertical = 0,
+ /// <summary>
+ /// Horizontal
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ Horizontal = 1,
+ }
+
+ /// <summary>
+ /// Container which contains RecycleItems.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public View Container { get; set; }
+
+ /// <summary>
+ /// Size of RecycleItem.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Size ItemSize { get; set; } = new Size();
+
+ /// <summary>
+ /// Get/Set the orientation in the layout.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public Orientation LayoutOrientation { get; set; } = Orientation.Vertical;
+
+ /// <summary>
+ /// How far can you reach the next item.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public float StepSize { get; protected set; }
+
+ /// <summary>
+ /// How far can you reach the next item.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int DataCount { get; set; }
+
+ /// <summary>
+ /// The last scrolled position which is calculated by ScrollableBase. The value should be updated in the Recycle() method.
+ /// </summary>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected float PrevScrollPosition { get; set; }
+
+ /// <summary>
+ /// This is called to find out where items are lain out according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void Layout(float scrollPosition)
+ {
+
+ }
+
+ /// <summary>
+ /// This is called to find out how much container size can be.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual float CalculateLayoutOrientationSize()
+ {
+ return 0.0f;
+ }
+
+ /// <summary>
+ /// This is called to find out which items should be recycled according to current scroll position.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <returns>List of RecycleItems which should be recycled.</returns>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual List<RecycleItem> Recycle(float scrollPosition)
+ {
+ return new List<RecycleItem>();
+ }
+
+ /// <summary>
+ /// Adjust scrolling position by own scrolling rules.
+ /// </summary>
+ /// <param name="scrollPosition">Scroll position which is calculated by ScrollableBase</param>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual float CalculateCandidateScrollPosition(float scrollPosition)
+ {
+ return scrollPosition;
+ }
+
+ /// <summary>
+ /// Gets the next keyboard focusable view in this control towards the given direction.<br />
+ /// A control needs to override this function in order to support two dimensional keyboard navigation.<br />
+ /// </summary>
+ /// <param name="currentFocusedView">The current focused view.</param>
+ /// <param name="direction">The direction to move the focus towards.</param>
+ /// <param name="loopEnabled">Whether the focus movement should be looped within the control.</param>
+ /// <returns>The next keyboard focusable view in this control or an empty handle if no view can be focused.</returns>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual View RequestNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ {
+ return null;
+ }
+
+ }
+}
--- /dev/null
+/* Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Wearable
+{
+ /// <summary>
+ /// [Draft] This class provides a View that can recycle items to improve performance.
+ /// </summary>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class RecyclerView : ScrollableBase
+ {
+ private RecycleAdapter adapter;
+ private RecycleLayoutManager layoutManager;
+ private int totalItemCount = 15;
+ private List<PropertyNotification> notifications = new List<PropertyNotification>();
+
+ public RecyclerView() : base()
+ {
+ Initialize(new RecycleAdapter(), new RecycleLayoutManager());
+ }
+
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ /// <param name="adapter">Recycle adapter of RecyclerView.</param>
+ /// <param name="layoutManager">Recycle layoutManager of RecyclerView.</param>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecyclerView(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
+ {
+ Initialize(adapter, layoutManager);
+ }
+
+ private void Initialize(RecycleAdapter adapter, RecycleLayoutManager layoutManager)
+ {
+ FocusGroup = true;
+ SetKeyboardNavigationSupport(true);
+ Scrolling += OnScrolling;
+
+ this.adapter = adapter;
+ this.adapter.OnDataChanged += OnAdapterDataChanged;
+
+ this.layoutManager = layoutManager;
+ this.layoutManager.Container = ContentContainer;
+ this.layoutManager.ItemSize = this.adapter.CreateRecycleItem().Size;
+ this.layoutManager.DataCount = this.adapter.Data.Count;
+
+ InitializeItems();
+ }
+
+ private void OnItemSizeChanged(object source, PropertyNotification.NotifyEventArgs args)
+ {
+ layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
+ }
+
+ public int TotalItemCount
+ {
+ get
+ {
+ return totalItemCount;
+ }
+ set
+ {
+ totalItemCount = value;
+ InitializeItems();
+ }
+ }
+
+ private void InitializeItems()
+ {
+ for (int i = Children.Count - 1; i > -1; i--)
+ {
+ Children[i].Unparent();
+ notifications[i].Notified -= OnItemSizeChanged;
+ notifications.RemoveAt(i);
+ }
+
+ for (int i = 0; i < totalItemCount; i++)
+ {
+ RecycleItem item = adapter.CreateRecycleItem();
+ item.DataIndex = i;
+ item.Name = "[" + i + "] recycle";
+
+ if (i < adapter.Data.Count)
+ {
+ adapter.BindData(item);
+ }
+ Add(item);
+
+ PropertyNotification noti = item.AddPropertyNotification("size", PropertyCondition.Step(0.1f));
+ noti.Notified += OnItemSizeChanged;
+ notifications.Add(noti);
+ }
+
+ layoutManager.Layout(0.0f);
+
+ if (ScrollingDirection == Direction.Horizontal)
+ {
+ ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
+ }
+ else
+ {
+ ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
+ }
+ }
+
+
+ public new Direction ScrollingDirection
+ {
+ get
+ {
+ return base.ScrollingDirection;
+ }
+ set
+ {
+ base.ScrollingDirection = value;
+
+ if (ScrollingDirection == Direction.Horizontal)
+ {
+ ContentContainer.SizeWidth = layoutManager.CalculateLayoutOrientationSize();
+ }
+ else
+ {
+ ContentContainer.SizeHeight = layoutManager.CalculateLayoutOrientationSize();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Recycler adpater.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecycleAdapter Adapter
+ {
+ get
+ {
+ return adapter;
+ }
+ set
+ {
+ if (adapter != null)
+ {
+ adapter.OnDataChanged -= OnAdapterDataChanged;
+ }
+
+ adapter = value;
+ adapter.OnDataChanged += OnAdapterDataChanged;
+ layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
+ layoutManager.DataCount = adapter.Data.Count;
+ InitializeItems();
+ }
+ }
+
+ /// <summary>
+ /// Recycler layoutManager.
+ /// </summary>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RecycleLayoutManager LayoutManager
+ {
+ get
+ {
+ return layoutManager;
+ }
+ set
+ {
+ layoutManager = value;
+ layoutManager.Container = ContentContainer;
+ layoutManager.ItemSize = adapter.CreateRecycleItem().Size;
+ layoutManager.DataCount = adapter.Data.Count;
+ InitializeItems();
+ }
+ }
+
+ private void OnScrolling(object source, ScrollEventArgs args)
+ {
+ layoutManager.Layout(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
+ List<RecycleItem> recycledItemList = layoutManager.Recycle(ScrollingDirection == Direction.Horizontal ? args.Position.X : args.Position.Y);
+ BindData(recycledItemList);
+ }
+
+ private void OnAdapterDataChanged(object source, EventArgs args)
+ {
+ List<RecycleItem> changedData = new List<RecycleItem>();
+
+ foreach (RecycleItem item in Children)
+ {
+ changedData.Add(item);
+ }
+
+ BindData(changedData);
+ }
+
+ private void BindData(List<RecycleItem> changedData)
+ {
+ foreach (RecycleItem item in changedData)
+ {
+ if (item.DataIndex > -1 && item.DataIndex < adapter.Data.Count)
+ {
+ item.Show();
+ item.Name = "[" + item.DataIndex + "]";
+ adapter.BindData(item);
+ }
+ else
+ {
+ item.Hide();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Adjust scrolling position by own scrolling rules.
+ /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
+ /// </summary>
+ /// <param name="position">Scroll position which is calculated by ScrollableBase</param>
+ /// <returns>Adjusted scroll destination</returns>
+ /// <since_tizen> 8 </since_tizen>
+ /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected override float AdjustTargetPositionOfScrollAnimation(float position)
+ {
+ // Destination is depending on implementation of layout manager.
+ // Get destination from layout manager.
+ return layoutManager.CalculateCandidateScrollPosition(position);
+ }
+
+ private View focusedView;
+ private int prevFocusedDataIndex = 0;
+
+ public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
+ {
+ View nextFocusedView = null;
+
+ if (!focusedView)
+ {
+ // If focusedView is null, find child which has previous data index
+ if (Children.Count > 0 && Adapter.Data.Count > 0)
+ {
+ for (int i = 0; i < Children.Count; i++)
+ {
+ RecycleItem item = Children[i] as RecycleItem;
+ if (item.DataIndex == prevFocusedDataIndex)
+ {
+ nextFocusedView = item;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ // If this is not first focus, request next focus to LayoutManager
+ if (LayoutManager != null)
+ {
+ nextFocusedView = LayoutManager.RequestNextFocusableView(currentFocusedView, direction, loopEnabled);
+ }
+ }
+
+ if (nextFocusedView != null)
+ {
+ // Check next focused view is inside of visible area.
+ // If it is not, move scroll position to make it visible.
+ Position scrollPosition = ContentContainer.CurrentPosition;
+ float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
+
+ float left = nextFocusedView.Position.X;
+ float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
+ float top = nextFocusedView.Position.Y;
+ float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
+
+ float visibleRectangleLeft = -scrollPosition.X;
+ float visibleRectangleRight = -scrollPosition.X + Size.Width;
+ float visibleRectangleTop = -scrollPosition.Y;
+ float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
+
+ if (ScrollingDirection == Direction.Horizontal)
+ {
+ if ((direction == View.FocusDirection.Left || direction == View.FocusDirection.Up) && left < visibleRectangleLeft)
+ {
+ targetPosition = left;
+ }
+ else if ((direction == View.FocusDirection.Right || direction == View.FocusDirection.Down) && right > visibleRectangleRight)
+ {
+ targetPosition = right - Size.Width;
+ }
+ }
+ else
+ {
+ if ((direction == View.FocusDirection.Up || direction == View.FocusDirection.Left) && top < visibleRectangleTop)
+ {
+ targetPosition = top;
+ }
+ else if ((direction == View.FocusDirection.Down || direction == View.FocusDirection.Right) && bottom > visibleRectangleBottom)
+ {
+ targetPosition = bottom - Size.Height;
+ }
+ }
+
+ focusedView = nextFocusedView;
+ if ((nextFocusedView as RecycleItem) != null)
+ {
+ prevFocusedDataIndex = (nextFocusedView as RecycleItem).DataIndex;
+ }
+
+ ScrollTo(targetPosition, true);
+ }
+ else
+ {
+ // If nextView is null, it means that we should move focus to outside of Control.
+ // Return FocusableView depending on direction.
+ switch (direction)
+ {
+ case View.FocusDirection.Left:
+ {
+ nextFocusedView = LeftFocusableView;
+ break;
+ }
+ case View.FocusDirection.Right:
+ {
+ nextFocusedView = RightFocusableView;
+ break;
+ }
+ case View.FocusDirection.Up:
+ {
+ nextFocusedView = UpFocusableView;
+ break;
+ }
+ case View.FocusDirection.Down:
+ {
+ nextFocusedView = DownFocusableView;
+ break;
+ }
+ }
+
+ if (nextFocusedView)
+ {
+ focusedView = null;
+ }
+ else
+ {
+ //If FocusableView doesn't exist, not move focus.
+ nextFocusedView = focusedView;
+ }
+ }
+
+ return nextFocusedView;
+ }
+ }
+}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-
-namespace Tizen.NUI.Binding
-{
- internal class DataTemplate : ElementTemplate
- {
- public DataTemplate()
- {
- }
-
- public DataTemplate(Type type) : base(type)
- {
- }
-
- public DataTemplate(Func<object> loadTemplate) : base(loadTemplate)
- {
- }
-
- public IDictionary<BindableProperty, BindingBase> Bindings { get; } = new Dictionary<BindableProperty, BindingBase>();
-
- public IDictionary<BindableProperty, object> Values { get; } = new Dictionary<BindableProperty, object>();
-
- public void SetBinding(BindableProperty property, BindingBase binding)
- {
- if (property == null)
- throw new ArgumentNullException(nameof(property));
- if (binding == null)
- throw new ArgumentNullException(nameof(binding));
-
- Values.Remove(property);
- Bindings[property] = binding;
- }
-
- public void SetValue(BindableProperty property, object value)
- {
- if (property == null)
- throw new ArgumentNullException(nameof(property));
-
- Bindings.Remove(property);
- Values[property] = value;
- }
-
- internal override void SetupContent(object item)
- {
- ApplyBindings(item);
- ApplyValues(item);
- }
-
- void ApplyBindings(object item)
- {
- if (Bindings == null)
- return;
-
- var bindable = item as BindableObject;
- if (bindable == null)
- return;
-
- foreach (KeyValuePair<BindableProperty, BindingBase> kvp in Bindings)
- {
- if (Values.ContainsKey(kvp.Key))
- throw new InvalidOperationException("Binding and Value found for " + kvp.Key.PropertyName);
-
- bindable.SetBinding(kvp.Key, kvp.Value.Clone());
- }
- }
-
- void ApplyValues(object item)
- {
- if (Values == null)
- return;
-
- var bindable = item as BindableObject;
- if (bindable == null)
- return;
- foreach (KeyValuePair<BindableProperty, object> kvp in Values)
- bindable.SetValue(kvp.Key, kvp.Value);
- }
- }
-}
+++ /dev/null
-using System.ComponentModel;
-
-namespace Tizen.NUI.Binding
-{
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal static class DataTemplateExtensions
- {
- public static DataTemplate SelectDataTemplate(this DataTemplate self, object item, BindableObject container)
- {
- var selector = self as DataTemplateSelector;
- if (selector == null)
- return self;
-
- return selector.SelectTemplate(item, container);
- }
-
- public static object CreateContent(this DataTemplate self, object item, BindableObject container)
- {
- return self.SelectDataTemplate(item, container).CreateContent();
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-
-namespace Tizen.NUI.Binding
-{
- internal abstract class DataTemplateSelector : DataTemplate
- {
- Dictionary<Type, DataTemplate> _dataTemplates = new Dictionary<Type, DataTemplate>();
-
- public DataTemplate SelectTemplate(object item, BindableObject container)
- {
- DataTemplate dataTemplate = null;
- if (_dataTemplates.TryGetValue(item.GetType(), out dataTemplate))
- {
- return dataTemplate;
- }
-
- dataTemplate = OnSelectTemplate(item, container);
- if (dataTemplate is DataTemplateSelector)
- throw new NotSupportedException(
- "DataTemplateSelector.OnSelectTemplate must not return another DataTemplateSelector");
-
- return dataTemplate;
- }
-
- protected abstract DataTemplate OnSelectTemplate(object item, BindableObject container);
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using Tizen.NUI.Binding.Internals;
-
-namespace Tizen.NUI.Binding
-{
- /// <summary>
- /// Base class for DataTemplate and ControlTemplate classes.
- /// </summary>
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal class ElementTemplate : IElement, IDataTemplate
- {
- List<Action<object, ResourcesChangedEventArgs>> _changeHandlers;
- Element _parent;
- bool _canRecycle; // aka IsDeclarative
-
- internal ElementTemplate()
- {
- }
-
- internal ElementTemplate(Type type) : this()
- {
- if (type == null)
- throw new ArgumentNullException(nameof(type));
-
- _canRecycle = true;
-
- LoadTemplate = () => Activator.CreateInstance(type);
- }
-
- internal ElementTemplate(Func<object> loadTemplate) : this()
- {
- if (loadTemplate == null)
- throw new ArgumentNullException(nameof(loadTemplate));
-
- LoadTemplate = loadTemplate;
- }
-
- Func<object> LoadTemplate { get; set; }
-
-#pragma warning disable 0612
- Func<object> IDataTemplate.LoadTemplate
- {
- get { return LoadTemplate; }
- set { LoadTemplate = value; }
- }
-#pragma warning restore 0612
-
- void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
- {
- _changeHandlers = _changeHandlers ?? new List<Action<object, ResourcesChangedEventArgs>>(1);
- _changeHandlers.Add(onchanged);
- }
-
- internal bool CanRecycle => _canRecycle;
- Element IElement.Parent
- {
- get { return _parent; }
- set
- {
- if (_parent == value)
- return;
- if (_parent != null)
- ((IElement)_parent).RemoveResourcesChangedListener(OnResourcesChanged);
- _parent = value;
- if (_parent != null)
- ((IElement)_parent).AddResourcesChangedListener(OnResourcesChanged);
- }
- }
-
- void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
- {
- if (_changeHandlers == null)
- return;
- _changeHandlers.Remove(onchanged);
- }
-
- /// <summary>
- /// Used by the XAML infrastructure to load data templates and set up the content of the resulting UI.
- /// </summary>
- /// <returns></returns>
- public object CreateContent()
- {
- if (LoadTemplate == null)
- throw new InvalidOperationException("LoadTemplate should not be null");
- if (this is DataTemplateSelector)
- throw new InvalidOperationException("Cannot call CreateContent directly on a DataTemplateSelector");
-
- object item = LoadTemplate();
- SetupContent(item);
-
- return item;
- }
-
- internal virtual void SetupContent(object item)
- {
- }
-
- void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
- {
- if (_changeHandlers == null)
- return;
- foreach (Action<object, ResourcesChangedEventArgs> handler in _changeHandlers)
- handler(this, e);
- }
- }
-}
+++ /dev/null
-using System;
-using System.ComponentModel;
-
-namespace Tizen.NUI.Binding.Internals
-{
- internal interface IDataTemplate
- {
- Func<object> LoadTemplate { get; set; }
- }
-}
--- /dev/null
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Binding
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class DataTemplate : ElementTemplate
+ {
+ /// <summary>
+ /// Base constructor.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DataTemplate()
+ {
+ }
+
+ /// <summary>
+ /// Base constructor with specific Type.
+ /// </summary>
+ /// <param name="Type">The Type of content.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DataTemplate(Type type) : base(type)
+ {
+ }
+
+ /// <summary>
+ /// Base constructor with loadTemplate function.
+ /// </summary>
+ /// <param name="loadTemplate">The function of loading templated object.</param>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DataTemplate(Func<object> loadTemplate) : base(loadTemplate)
+ {
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public IDictionary<BindableProperty, BindingBase> Bindings { get; } = new Dictionary<BindableProperty, BindingBase>();
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public IDictionary<BindableProperty, object> Values { get; } = new Dictionary<BindableProperty, object>();
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetBinding(BindableProperty property, BindingBase binding)
+ {
+ if (property == null)
+ throw new ArgumentNullException(nameof(property));
+ if (binding == null)
+ throw new ArgumentNullException(nameof(binding));
+
+ Values.Remove(property);
+ Bindings[property] = binding;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetValue(BindableProperty property, object value)
+ {
+ if (property == null)
+ throw new ArgumentNullException(nameof(property));
+
+ Bindings.Remove(property);
+ Values[property] = value;
+ }
+
+ internal override void SetupContent(object item)
+ {
+ ApplyBindings(item);
+ ApplyValues(item);
+ }
+
+ void ApplyBindings(object item)
+ {
+ if (Bindings == null)
+ return;
+
+ var bindable = item as BindableObject;
+ if (bindable == null)
+ return;
+
+ foreach (KeyValuePair<BindableProperty, BindingBase> kvp in Bindings)
+ {
+ if (Values.ContainsKey(kvp.Key))
+ throw new InvalidOperationException("Binding and Value found for " + kvp.Key.PropertyName);
+
+ bindable.SetBinding(kvp.Key, kvp.Value.Clone());
+ }
+ }
+
+ void ApplyValues(object item)
+ {
+ if (Values == null)
+ return;
+
+ var bindable = item as BindableObject;
+ if (bindable == null)
+ return;
+ foreach (KeyValuePair<BindableProperty, object> kvp in Values)
+ bindable.SetValue(kvp.Key, kvp.Value);
+ }
+ }
+}
--- /dev/null
+using System.ComponentModel;
+
+namespace Tizen.NUI.Binding
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static class DataTemplateExtensions
+ {
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static DataTemplate SelectDataTemplate(this DataTemplate self, object item, BindableObject container)
+ {
+ var selector = self as DataTemplateSelector;
+ if (selector == null)
+ return self;
+
+ return selector.SelectTemplate(item, container);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static object CreateContent(this DataTemplate self, object item, BindableObject container)
+ {
+ return self.SelectDataTemplate(item, container).CreateContent();
+ }
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+
+namespace Tizen.NUI.Binding
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public abstract class DataTemplateSelector : DataTemplate
+ {
+ Dictionary<Type, DataTemplate> _dataTemplates = new Dictionary<Type, DataTemplate>();
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public DataTemplate SelectTemplate(object item, BindableObject container)
+ {
+ DataTemplate dataTemplate = null;
+ if (_dataTemplates.TryGetValue(item.GetType(), out dataTemplate))
+ {
+ return dataTemplate;
+ }
+
+ dataTemplate = OnSelectTemplate(item, container);
+ if (dataTemplate is DataTemplateSelector)
+ throw new NotSupportedException(
+ "DataTemplateSelector.OnSelectTemplate must not return another DataTemplateSelector");
+
+ return dataTemplate;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected abstract DataTemplate OnSelectTemplate(object item, BindableObject container);
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Tizen.NUI.Binding.Internals;
+
+namespace Tizen.NUI.Binding
+{
+ /// <summary>
+ /// Base class for DataTemplate and ControlTemplate classes.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public class ElementTemplate : IElement, IDataTemplate
+ {
+ List<Action<object, ResourcesChangedEventArgs>> _changeHandlers;
+ Element _parent;
+ bool _canRecycle; // aka IsDeclarative
+
+ internal ElementTemplate()
+ {
+ }
+
+ internal ElementTemplate(Type type) : this()
+ {
+ if (type == null)
+ throw new ArgumentNullException(nameof(type));
+
+ _canRecycle = true;
+
+ LoadTemplate = () => Activator.CreateInstance(type);
+ }
+
+ internal ElementTemplate(Func<object> loadTemplate) : this()
+ {
+ if (loadTemplate == null)
+ throw new ArgumentNullException(nameof(loadTemplate));
+
+ LoadTemplate = loadTemplate;
+ }
+
+ Func<object> LoadTemplate { get; set; }
+
+#pragma warning disable 0612
+ Func<object> IDataTemplate.LoadTemplate
+ {
+ get { return LoadTemplate; }
+ set { LoadTemplate = value; }
+ }
+#pragma warning restore 0612
+
+ void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
+ {
+ _changeHandlers = _changeHandlers ?? new List<Action<object, ResourcesChangedEventArgs>>(1);
+ _changeHandlers.Add(onchanged);
+ }
+
+ internal bool CanRecycle => _canRecycle;
+ Element IElement.Parent
+ {
+ get { return _parent; }
+ set
+ {
+ if (_parent == value)
+ return;
+ if (_parent != null)
+ ((IElement)_parent).RemoveResourcesChangedListener(OnResourcesChanged);
+ _parent = value;
+ if (_parent != null)
+ ((IElement)_parent).AddResourcesChangedListener(OnResourcesChanged);
+ }
+ }
+
+ void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
+ {
+ if (_changeHandlers == null)
+ return;
+ _changeHandlers.Remove(onchanged);
+ }
+
+ /// <summary>
+ /// Used by the XAML infrastructure to load data templates and set up the content of the resulting UI.
+ /// </summary>
+ /// <returns></returns>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public object CreateContent()
+ {
+ if (LoadTemplate == null)
+ throw new InvalidOperationException("LoadTemplate should not be null");
+ if (this is DataTemplateSelector)
+ throw new InvalidOperationException("Cannot call CreateContent directly on a DataTemplateSelector");
+
+ object item = LoadTemplate();
+ SetupContent(item);
+
+ return item;
+ }
+
+ internal virtual void SetupContent(object item)
+ {
+ }
+
+ void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
+ {
+ if (_changeHandlers == null)
+ return;
+ foreach (Action<object, ResourcesChangedEventArgs> handler in _changeHandlers)
+ handler(this, e);
+ }
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+
+namespace Tizen.NUI.Binding
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IDataTemplate
+ {
+ Func<object> LoadTemplate { get; set; }
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Samples
+{
+ public class CollectionViewGridSample : IExample
+ {
+ CollectionView colView;
+ int itemCount = 500;
+ int selectedCount;
+ ItemSelectionMode selMode;
+
+ public void Activate()
+ {
+ Window window = NUIApplication.GetDefaultWindow();
+
+ var myViewModelSource = new GalleryViewModel(itemCount);
+ selMode = ItemSelectionMode.MultipleSelections;
+ DefaultTitleItem myTitle = new DefaultTitleItem();
+ myTitle.Text = "Grid Sample Count["+itemCount+"] Selected["+selectedCount+"]";
+ //Set Width Specification as MatchParent to fit the Item width with parent View.
+ myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
+
+ colView = new CollectionView()
+ {
+ ItemsSource = myViewModelSource,
+ ItemsLayouter = new GridLayouter(),
+ ItemTemplate = new DataTemplate(() =>
+ {
+ DefaultGridItem item = new DefaultGridItem();
+ item.WidthSpecification = 180;
+ item.HeightSpecification = 240;
+ //Decorate Label
+ item.Caption.SetBinding(TextLabel.TextProperty, "ViewLabel");
+ item.Caption.HorizontalAlignment = HorizontalAlignment.Center;
+ //Decorate Image
+ item.Image.SetBinding(ImageView.ResourceUrlProperty, "ImageUrl");
+ item.Image.WidthSpecification = 170;
+ item.Image.HeightSpecification = 170;
+ //Decorate Badge checkbox.
+ //[NOTE] This is sample of CheckBox usage in CollectionView.
+ // Checkbox change their selection by IsSelectedProperty bindings with
+ // SelectionChanged event with MulitpleSelections ItemSelectionMode of CollectionView.
+ item.Badge = new CheckBox();
+ //FIXME : SetBinding in RadioButton crashed as Sensitive Property is disposed.
+ //item.Badge.SetBinding(CheckBox.IsSelectedProperty, "Selected");
+ item.Badge.WidthSpecification = 30;
+ item.Badge.HeightSpecification = 30;
+
+ return item;
+ }),
+ Header = myTitle,
+ ScrollingDirection = ScrollableBase.Direction.Vertical,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ SelectionMode = selMode
+ };
+ colView.SelectionChanged += SelectionEvt;
+
+ window.Add(colView);
+ }
+
+ public void SelectionEvt(object sender, SelectionChangedEventArgs ev)
+ {
+ List<object> oldSel = new List<object>(ev.PreviousSelection);
+ List<object> newSel = new List<object>(ev.CurrentSelection);
+
+ foreach (object item in oldSel)
+ {
+ if (item != null && item is Gallery)
+ {
+ Gallery galItem = (Gallery)item;
+ if (!(newSel.Contains(item)))
+ {
+ galItem.Selected = false;
+ Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel);
+ selectedCount--;
+ }
+ }
+ else continue;
+ }
+ foreach (object item in newSel)
+ {
+ if (item != null && item is Gallery)
+ {
+ Gallery galItem = (Gallery)item;
+ if (!(oldSel.Contains(item)))
+ {
+ galItem.Selected = true;
+ Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel);
+ selectedCount++;
+ }
+ }
+ else continue;
+ }
+ if (colView.Header != null && colView.Header is DefaultTitleItem)
+ {
+ DefaultTitleItem title = (DefaultTitleItem)colView.Header;
+ title.Text = "Grid Sample Count["+itemCount+"] Selected["+selectedCount+"]";
+ }
+ }
+
+ public void Deactivate()
+ {
+ if (colView != null)
+ {
+ colView.Dispose();
+ }
+ }
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+using System.ComponentModel;
+using System;
+
+namespace Tizen.NUI.Samples
+{
+ public class CollectionViewLinearSample : IExample
+ {
+ CollectionView colView;
+ int itemCount = 500;
+ string selectedItem;
+ ItemSelectionMode selMode;
+
+ public void Activate()
+ {
+ Window window = NUIApplication.GetDefaultWindow();
+
+ var myViewModelSource = new GalleryViewModel(itemCount);
+ selMode = ItemSelectionMode.SingleSelection;
+ DefaultTitleItem myTitle = new DefaultTitleItem();
+ myTitle.Text = "Linear Sample Count["+itemCount+"]";
+ //Set Width Specification as MatchParent to fit the Item width with parent View.
+ myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
+
+ colView = new CollectionView()
+ {
+ ItemsSource = myViewModelSource,
+ 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;
+ //Decorate Label
+ item.Label.SetBinding(TextLabel.TextProperty, "ViewLabel");
+ item.Label.HorizontalAlignment = HorizontalAlignment.Begin;
+ //Decorate Icon
+ item.Icon.SetBinding(ImageView.ResourceUrlProperty, "ImageUrl");
+ item.Icon.WidthSpecification = 80;
+ item.Icon.HeightSpecification = 80;
+ //Decorate Extra RadioButton.
+ //[NOTE] This is sample of RadioButton usage in CollectionView.
+ // RadioButton change their selection by IsSelectedProperty bindings with
+ // SelectionChanged event with SingleSelection ItemSelectionMode of CollectionView.
+ // be aware of there are no RadioButtonGroup.
+ item.Extra = new RadioButton();
+ //FIXME : SetBinding in RadioButton crashed as Sensitive Property is disposed.
+ //item.Extra.SetBinding(RadioButton.IsSelectedProperty, "Selected");
+ item.Extra.WidthSpecification = 80;
+ item.Extra.HeightSpecification = 80;
+ */
+
+ return item;
+ }),
+ Header = myTitle,
+ ScrollingDirection = ScrollableBase.Direction.Vertical,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ SelectionMode = selMode
+ };
+ colView.SelectionChanged += SelectionEvt;
+
+ window.Add(colView);
+
+ }
+
+ public void SelectionEvt(object sender, SelectionChangedEventArgs ev)
+ {
+ //Tizen.Log.Debug("NUI", "LSH :: SelectionEvt called");
+
+ //SingleSelection Only have 1 or nil object in the list.
+ 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);
+ }
+ 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 (colView.Header != null && colView.Header is DefaultTitleItem)
+ {
+ DefaultTitleItem title = (DefaultTitleItem)colView.Header;
+ title.Text = "Linear Sample Count[" + itemCount + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]");
+ }
+ }
+ public void Deactivate()
+ {
+ if (colView != null)
+ {
+ colView.Dispose();
+ }
+ }
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+
+
+
+class Gallery : INotifyPropertyChanged
+{
+ string sourceDir = Tizen.NUI.Samples.CommonResource.GetDaliResourcePath()+"ItemViewDemo/gallery/gallery-medium-";
+ private int index;
+ private string name;
+ private bool selected;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void OnPropertyyChanged(string propertyName)
+ {
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ public Gallery(int galleryIndex, string galleryName)
+ {
+ index = galleryIndex;
+ name = galleryName;
+ }
+
+ public string Name {
+ get
+ {
+ return name;
+ }
+ set
+ {
+ name = value;
+ OnPropertyyChanged("Name");
+ OnPropertyyChanged("ViewLabel");
+ }
+ }
+ public string ViewLabel
+ {
+ get
+ {
+ return "[" + index + "] : " + name;
+ }
+ }
+
+ public string ImageUrl
+ {
+ get
+ {
+ return sourceDir+(index%20)+".jpg";
+ }
+ }
+
+ public bool Selected {
+ get
+ {
+ return selected;
+ }
+ set
+ {
+ selected = value;
+ OnPropertyyChanged("Selected");
+ }
+ }
+}
+
+class Album : ObservableCollection<Gallery>
+{
+ private int index;
+ private string name;
+ private DateTime date;
+
+ public Album(int albumIndex, string albumName, DateTime albumDate)
+ {
+ index = albumIndex;
+ name = albumName;
+ date = albumDate;
+ }
+
+ public string Title
+ {
+ get
+ {
+ return "[" + index + "] " + name;
+ }
+ }
+
+ public string Date
+ {
+ get
+ {
+ return date.ToLongDateString();
+ }
+ }
+}
+
+class GalleryViewModel : ObservableCollection<Gallery>
+{
+ string[] namePool = {
+ "Cat",
+ "Boy",
+ "Arm muscle",
+ "Girl",
+ "House",
+ "Cafe",
+ "Statue",
+ "Sea",
+ "hosepipe",
+ "Police",
+ "Rainbow",
+ "Icicle",
+ "Tower with the Moon",
+ "Giraffe",
+ "Camel",
+ "Zebra",
+ "Red Light",
+ "Banana",
+ "Lion",
+ "Espresso",
+ };
+ public GalleryViewModel(int count)
+ {
+ CreateData(this, count);
+ }
+
+ public ObservableCollection<Gallery> CreateData(ObservableCollection<Gallery> result , int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ result.Add(new Gallery(i, namePool[i%20]));
+ }
+ return result;
+ }
+}
+
+class AlbumViewModel : ObservableCollection<Album>
+{
+ string[] namePool = {
+ "Cat",
+ "Boy",
+ "Arm muscle",
+ "Girl",
+ "House",
+ "Cafe",
+ "Statue",
+ "Sea",
+ "hosepipe",
+ "Police",
+ "Rainbow",
+ "Icicle",
+ "Tower with the Moon",
+ "Giraffe",
+ "Camel",
+ "Zebra",
+ "Red Light",
+ "Banana",
+ "Lion",
+ "Espresso",
+ };
+
+ (string name, DateTime date)[] titlePool = {
+ ("House Move", new DateTime(2021, 2, 26)),
+ ("Covid 19", new DateTime(2020, 1, 20)),
+ ("Porto Trip", new DateTime(2019, 11, 23)),
+ ("Granada Trip", new DateTime(2019, 11, 20)),
+ ("Barcelona Trip", new DateTime(2019, 11, 17)),
+ ("Developer's Day", new DateTime(2019, 11, 16)),
+ ("Tokyo Trip", new DateTime(2019, 7, 5)),
+ ("Otaru Trip", new DateTime(2019, 3, 2)),
+ ("Sapporo Trip", new DateTime(2019, 2, 28)),
+ ("Hakodate Trip", new DateTime(2019, 2, 26)),
+ ("Friend's Wedding", new DateTime(2018, 11, 23)),
+ ("Grandpa Birthday", new DateTime(2018, 9, 14)),
+ ("Family Jeju Trip", new DateTime(2018, 7, 15)),
+ ("HongKong Trip", new DateTime(2018, 3, 30)),
+ ("Mom's Birthday", new DateTime(2017, 12, 21)),
+ ("Buy new Car", new DateTime(2017, 10, 18)),
+ ("Graduation", new DateTime(2017, 6, 30)),
+ };
+
+
+ public AlbumViewModel()
+ {
+ CreateData(this);
+ }
+
+ public ObservableCollection<Album> CreateData(ObservableCollection<Album> result)
+ {
+ for (int i = 0; i < titlePool.Length; i++)
+ {
+ (string name, DateTime date) = titlePool[i];
+ Album cur = new Album(i, name, date);
+ for (int j = 0; j < 20; j++)
+ {
+ cur.Add(new Gallery(j, namePool[j]));
+ }
+ result.Add(cur);
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Samples
+{
+ public class CollectionViewGridGroupSample : IExample
+ {
+ CollectionView colView;
+ int selectedCount;
+ ItemSelectionMode selMode;
+ ObservableCollection<Album> groupSource;
+
+ public void Activate()
+ {
+ Window window = NUIApplication.GetDefaultWindow();
+
+ groupSource = new AlbumViewModel();
+ selMode = ItemSelectionMode.MultipleSelections;
+ DefaultTitleItem myTitle = new DefaultTitleItem();
+ myTitle.Text = "Grid Sample Count["+ groupSource.Count+"] Selected["+selectedCount+"]";
+ //Set Width Specification as MatchParent to fit the Item width with parent View.
+ myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
+
+ colView = new CollectionView()
+ {
+ ItemsSource = groupSource,
+ ItemsLayouter = new GridLayouter(),
+ ItemTemplate = new DataTemplate(() =>
+ {
+ DefaultGridItem item = new DefaultGridItem();
+ item.WidthSpecification = 180;
+ item.HeightSpecification = 240;
+ //Decorate Label
+ item.Caption.SetBinding(TextLabel.TextProperty, "ViewLabel");
+ item.Caption.HorizontalAlignment = HorizontalAlignment.Center;
+ //Decorate Image
+ item.Image.SetBinding(ImageView.ResourceUrlProperty, "ImageUrl");
+ item.Image.WidthSpecification = 170;
+ item.Image.HeightSpecification = 170;
+ //Decorate Badge checkbox.
+ //[NOTE] This is sample of CheckBox usage in CollectionView.
+ // Checkbox change their selection by IsSelectedProperty bindings with
+ // SelectionChanged event with MulitpleSelections ItemSelectionMode of CollectionView.
+ item.Badge = new CheckBox();
+ //FIXME : SetBinding in RadioButton crashed as Sensitive Property is disposed.
+ //item.Badge.SetBinding(CheckBox.IsSelectedProperty, "Selected");
+ item.Badge.WidthSpecification = 30;
+ item.Badge.HeightSpecification = 30;
+
+ return item;
+ }),
+ GroupHeaderTemplate = new DataTemplate(() =>
+ {
+ 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 group;
+ }),
+ Header = myTitle,
+ IsGrouped = true,
+ ScrollingDirection = ScrollableBase.Direction.Vertical,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ SelectionMode = selMode
+ };
+ colView.SelectionChanged += SelectionEvt;
+
+ window.Add(colView);
+ }
+
+ public void SelectionEvt(object sender, SelectionChangedEventArgs ev)
+ {
+ List<object> oldSel = new List<object>(ev.PreviousSelection);
+ List<object> newSel = new List<object>(ev.CurrentSelection);
+
+ foreach (object item in oldSel)
+ {
+ if (item != null && item is Gallery)
+ {
+ Gallery galItem = (Gallery)item;
+ if (!(newSel.Contains(item)))
+ {
+ galItem.Selected = false;
+ Tizen.Log.Debug("Unselected: {0}", galItem.ViewLabel);
+ selectedCount--;
+ }
+ }
+ else continue;
+ }
+ foreach (object item in newSel)
+ {
+ if (item != null && item is Gallery)
+ {
+ Gallery galItem = (Gallery)item;
+ if (!(oldSel.Contains(item)))
+ {
+ galItem.Selected = true;
+ Tizen.Log.Debug("Selected: {0}", galItem.ViewLabel);
+ selectedCount++;
+ }
+ }
+ else continue;
+ }
+ if (colView.Header != null && colView.Header is DefaultTitleItem)
+ {
+ DefaultTitleItem title = (DefaultTitleItem)colView.Header;
+ title.Text = "Grid Sample Count["+ groupSource.Count + "] Selected["+selectedCount+"]";
+ }
+ }
+
+ public void Deactivate()
+ {
+ if (colView != null)
+ {
+ colView.Dispose();
+ }
+ }
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.NUI.Binding;
+
+namespace Tizen.NUI.Samples
+{
+ public class CollectionViewLinearGroupSample : IExample
+ {
+ CollectionView colView;
+ string selectedItem;
+ ItemSelectionMode selMode;
+ ObservableCollection<Album> groupSource;
+
+ public void Activate()
+ {
+ Window window = NUIApplication.GetDefaultWindow();
+
+ groupSource = new AlbumViewModel();
+ 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+"]";
+ //Set Width Specification as MatchParent to fit the Item width with parent View.
+ myTitle.WidthSpecification = LayoutParamPolicies.MatchParent;
+
+ colView = new CollectionView()
+ {
+ ItemsSource = groupSource,
+ 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;
+ //Decorate Label
+ item.Label.SetBinding(TextLabel.TextProperty, "ViewLabel");
+ item.Label.HorizontalAlignment = HorizontalAlignment.Begin;
+ //Decorate Icon
+ item.Icon.SetBinding(ImageView.ResourceUrlProperty, "ImageUrl");
+ item.Icon.WidthSpecification = 80;
+ item.Icon.HeightSpecification = 80;
+ //Decorate Extra RadioButton.
+ //[NOTE] This is sample of RadioButton usage in CollectionView.
+ // RadioButton change their selection by IsSelectedProperty bindings with
+ // SelectionChanged event with SingleSelection ItemSelectionMode of CollectionView.
+ // be aware of there are no RadioButtonGroup.
+ item.Extra = new RadioButton();
+ //FIXME : SetBinding in RadioButton crashed as Sensitive Property is disposed.
+ //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;
+ }),
+ Header = myTitle,
+ IsGrouped = true,
+ ScrollingDirection = ScrollableBase.Direction.Vertical,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ SelectionMode = selMode
+ };
+ colView.SelectionChanged += SelectionEvt;
+
+ window.Add(colView);
+
+ }
+
+ public void SelectionEvt(object sender, SelectionChangedEventArgs ev)
+ {
+ //Tizen.Log.Debug("NUI", "LSH :: SelectionEvt called");
+
+ //SingleSelection Only have 1 or nil object in the list.
+ 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);
+ }
+ 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 (colView.Header != null && colView.Header is DefaultTitleItem)
+ {
+ DefaultTitleItem title = (DefaultTitleItem)colView.Header;
+ title.Text = "Linear Sample Count[" + groupSource + (selectedItem != null ? "] Selected [" + selectedItem + "]" : "]");
+ }
+ }
+ public void Deactivate()
+ {
+ if (colView != null)
+ {
+ colView.Dispose();
+ }
+ }
+ }
+}