diff options
Diffstat (limited to 'Xamarin.Forms.Platform.Tizen/Native/ListView.cs')
-rw-r--r-- | Xamarin.Forms.Platform.Tizen/Native/ListView.cs | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.Tizen/Native/ListView.cs b/Xamarin.Forms.Platform.Tizen/Native/ListView.cs new file mode 100644 index 00000000..d5531dc4 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ListView.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Type alias which identifies list of cells whose data model was transformed by Xamarin. + /// </summary> + using GroupList = TemplatedItemsList<ItemsView<Cell>, Cell>; + + /// <summary> + /// Native ListView implementation for Xamarin renderer + /// </summary> + /// <remarks> + /// This internally uses GenList class. + /// One should note that it is optimized for displaying many elements which may be + /// unavailable at first. This means that only currently visible elements will be constructed. + /// Whenever element disappears from visible space its content is destroyed for time being. + /// This is carried out by so called Cell Handlers. + /// </remarks> + public class ListView : GenList + { + /// <summary> + /// ItemContext helper class. This represents the association between Xamarin.Forms.Cell and + /// native elements. It also stores useful context for them. + /// </summary> + public class ItemContext + { + public ItemContext() + { + Item = null; + Cell = null; + Renderer = null; + ListOfSubItems = null; + } + + public GenListItem Item; + public Cell Cell; + public bool IsGroupItem; + public CellRenderer Renderer; + internal TemplatedItemsList<ItemsView<Cell>, Cell> ListOfSubItems; + } + + /// <summary> + /// The item context list for each added element. + /// </summary> + readonly List<ItemContext> _itemContextList = new List<ItemContext>(); + + /// <summary> + /// Registered cell handlers. + /// </summary> + protected readonly IDictionary<Type, CellRenderer> _cellRendererCache = new Dictionary<Type, CellRenderer>(); + + /// <summary> + /// Registered group handlers. + /// </summary> + protected readonly IDictionary<Type, CellRenderer> _groupCellRendererCache = new Dictionary<Type, CellRenderer>(); + + /// <summary> + /// The header context. + /// </summary> + ItemContext _headerContext; + + /// <summary> + /// The header element. + /// </summary> + VisualElement _headerElement; + + /// <summary> + /// The footer context. + /// </summary> + ItemContext _footerContext; + + /// <summary> + /// The footer element. + /// </summary> + VisualElement _footerElement; + + /// <summary> + /// The item class for header and footer. + /// </summary> + GenItemClass _headerFooterItemClass = null; + + /// <summary> + /// Gets or sets a value indicating whether this instance has grouping enabled. + /// </summary> + /// <value><c>true</c> if this instance has grouping enabled.</value> + public bool IsGroupingEnabled { get; set; } + + /// <summary> + /// Constructor of ListView native control. + /// </summary> + /// <param name="parent">ElmSharp object which is parent of particular list view</param> + public ListView(EvasObject parent) + : base(parent) + { + ItemRealized += OnItemAppear; + ItemUnrealized += OnItemDisappear; + } + + /// <summary> + /// Gets the item context based on Cell item. + /// </summary> + /// <returns>The item context.</returns> + /// <param name="cell">Cell for which context should be found.</param> + internal ItemContext GetItemContext(Cell cell) + { + if (cell == null) + { + return null; + } + else + { + return _itemContextList.Find(X => X.Cell == cell); + } + } + + /// <summary> + /// Sets the HasUnevenRows property. + /// </summary> + /// <param name="hasUnevenRows">If <c>true</c>, the list will allow uneven sizes for its rows.</param> + public void SetHasUnevenRows(bool hasUnevenRows) + { + Homogeneous = !hasUnevenRows; + UpdateRealizedItems(); + } + + /// <summary> + /// Adds elements to the list and defines its presentation based on Cell type. + /// </summary> + /// <param name="_source">IEnumerable on Cell collection.</param> + /// <param name="beforeCell">Cell before which new items will be placed. + /// Null value may also be passed as this parameter, which results in appending new items to the end. + /// </param> + public void AddSource(IEnumerable _source, Cell beforeCell = null) + { + foreach (var data in _source) + { + GroupList groupList = data as GroupList; + if (groupList != null) + { + AddGroupItem(groupList, beforeCell); + foreach (var item in groupList) + { + AddItem(item as Cell, groupList.HeaderContent); + } + } + else + { + AddItem(data as Cell, null, beforeCell); + } + } + } + + /// <summary> + /// Deletes all items from a given group. + /// </summary> + /// <param name="group">Group of items to be deleted.</param> + internal void ResetGroup(GroupList group) + { + var items = _itemContextList.FindAll(x => x.ListOfSubItems == group && x.Cell != group.HeaderContent); + foreach (var item in items) + { + item.Item?.Delete(); + } + } + + /// <summary> + /// Adds items to the group. + /// </summary> + /// <param name="itemGroup">Group to which elements will be added.</param> + /// <param name="newItems">New list items to be added.</param> + /// <param name="cellBefore">A reference to the Cell already existing in a ListView. + /// Newly added cells will be put just before this cell.</param> + public void AddItemsToGroup(IEnumerable itemGroup, IEnumerable newItems, Cell cellBefore = null) + { + ItemContext groupCtx = GetItemContext((itemGroup as GroupList)?.HeaderContent); + if (groupCtx != null) + { + foreach (var item in newItems) + { + AddItem(item as Cell, groupCtx.Cell, cellBefore); + } + } + } + + /// <summary> + /// Removes the specified cells. + /// </summary> + /// <param name="cells">Cells to be removed.</param> + public void Remove(IEnumerable cells) + { + foreach (var data in cells) + { + var group = data as GroupList; + if (group != null) + { + ItemContext groupCtx = GetItemContext(group.HeaderContent); + Remove(groupCtx.ListOfSubItems); + groupCtx.Item.Delete(); + } + else + { + ItemContext itemCtx = GetItemContext(data as Cell); + itemCtx?.Item?.Delete(); + } + } + } + + /// <summary> + /// Scrolls the list to a specified cell. + /// </summary> + /// <remarks> + /// Different scrolling behaviors are also possible. The element may be positioned in the center, + /// top or bottom of the visible part of the list depending on the value of the <c>position</c> parameter. + /// </remarks> + /// <param name="cell">Cell which will be displayed after scrolling .</param> + /// <param name="position">This will defines scroll to behavior based on ScrollToPosition values.</param> + /// <param name="animated">If <c>true</c>, scrolling will be animated. Otherwise the cell will be moved instantaneously.</param> + public void ApplyScrollTo(Cell cell, ScrollToPosition position, bool animated) + { + GenListItem item = GetItemContext(cell)?.Item; + if (item != null) + this.ScrollTo(item, position.ToNative(), animated); + } + + /// <summary> + /// Selects the specified cell. + /// </summary> + /// <param name="cell">Cell to be selected.</param> + public void ApplySelectedItem(Cell cell) + { + GenListItem item = GetItemContext(cell)?.Item; + if (item != null) + item.IsSelected = true; + } + + /// <summary> + /// Sets the header. + /// </summary> + /// <param name="header">Header of the list.</param> + public void SetHeader(VisualElement header) + { + if (header == null) + { + if (HasHeader()) + { + RemoveHeader(); + } + + return; + } + + GenItemClass headerTemplate = GetHeaderFooterItemClass(); + + _headerElement = header; + if (HasHeader()) + { + FirstItem.UpdateItemClass(headerTemplate, header); + } + else + { + _headerContext = new ItemContext(); + _headerContext.Item = _itemContextList.Count > 0 ? InsertBefore(headerTemplate, header, FirstItem) : Append(headerTemplate, header); + _headerContext.Item.SelectionMode = GenListSelectionMode.None; + _headerContext.Item.Deleted += HeaderDeletedHandler; + _itemContextList.Insert(0, _headerContext); + } + } + + /// <summary> + /// Sets the footer. + /// </summary> + /// <param name="footer">Footer of the list.</param> + public void SetFooter(VisualElement footer) + { + if (footer == null) + { + if (HasFooter()) + { + RemoveFooter(); + } + return; + } + + GenItemClass footerTemplate = GetHeaderFooterItemClass(); + + _footerElement = footer; + if (HasFooter()) + { + _footerContext.Item.UpdateItemClass(footerTemplate, footer); + } + else + { + _footerContext = new ItemContext(); + _footerContext.Item = Append(footerTemplate, footer); + _footerContext.Item.SelectionMode = GenListSelectionMode.None; + _footerContext.Item.Deleted += FooterDeletedHandler; + _itemContextList.Add(_footerContext); + } + } + + /// <summary> + /// Removes the header. + /// </summary> + public void RemoveHeader() + { + _itemContextList.Remove(_headerContext); + _headerContext?.Item?.Delete(); + _headerContext = null; + _headerElement = null; + } + + /// <summary> + /// Removes the footer. + /// </summary> + public void RemoveFooter() + { + _itemContextList.Remove(_footerContext); + _footerContext?.Item?.Delete(); + _footerContext = null; + _footerElement = null; + } + + /// <summary> + /// Determines whether this instance has a header. + /// </summary> + /// <returns><c>true</c> if the header is present.</returns> + public bool HasHeader() + { + return _headerContext != null; + } + + /// <summary> + /// Determines whether this instance has a footer. + /// </summary> + /// <returns><c>true</c> if the footer is present.</returns> + public bool HasFooter() + { + return _footerContext != null; + } + + /// <summary> + /// Gets the header. + /// </summary> + /// <returns>The header.</returns> + public VisualElement GetHeader() + { + return _headerElement; + } + + /// <summary> + /// Gets the footer. + /// </summary> + /// <returns>The footer.</returns> + public VisualElement GetFooter() + { + return _footerElement; + } + + /// <summary> + /// Handles the header deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void HeaderDeletedHandler(object sender, EventArgs e) + { + _itemContextList.Remove(_headerContext); + _headerContext = null; + } + + /// <summary> + /// Handles the footer deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void FooterDeletedHandler(object sender, EventArgs e) + { + _itemContextList.Remove(_footerContext); + _footerContext = null; + } + + /// <summary> + /// Called every time an object gets realized. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="evt">GenListItemEventArgs.</param> + void OnItemAppear(object sender, GenListItemEventArgs evt) + { + ItemContext itemContext = (evt.Item.Data as ItemContext); + + if (itemContext != null && itemContext.Cell != null) + { + (itemContext.Cell as ICellController).SendAppearing(); + } + } + + /// <summary> + /// Called every time an object gets unrealized. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="evt">GenListItemEventArgs.</param> + void OnItemDisappear(object sender, GenListItemEventArgs evt) + { + ItemContext itemContext = (evt.Item.Data as ItemContext); + if (itemContext != null && itemContext.Cell != null) + { + (itemContext.Cell as ICellController).SendDisappearing(); + itemContext.Renderer?.SendUnrealizedCell(itemContext.Cell); + } + } + + + /// <summary> + /// A convenience shorthand method for derivate classes. + /// </summary> + /// <param name="cell">Cell to be added.</param> + protected void AddCell(Cell cell) + { + AddItem(cell); + } + + /// <summary> + /// Gets the cell renderer for given cell type. + /// </summary> + /// <returns>The cell handler.</returns> + /// <param name="cell">Cell to be added.</param> + /// <param name="isGroup">If <c>true</c>, then group handlers will be included in the lookup as well.</param> + protected virtual CellRenderer GetCellRenderer(Cell cell, bool isGroup = false) + { + Type type = cell.GetType(); + var cache = isGroup ? _groupCellRendererCache : _cellRendererCache; + if (cache.ContainsKey(type)) + return cache[type]; + + CellRenderer renderer = null; + + if (isGroup && type == typeof(TextCell)) + { + renderer = new GroupCellTextRenderer(); + } + renderer = renderer ?? Registrar.Registered.GetHandler<CellRenderer>(type); + + if (renderer == null) + { + Log.Error("Cell type is not handled: {0}", cell.GetType()); + throw new ArgumentNullException("Unsupported cell type"); + } + return cache[type] = renderer; + } + + /// <summary> + /// Adds the group item. Group item is actually of class GroupList because + /// group item has sub items (can be zero) which needs to be added. + /// If beforeCell is not null, new group will be added just before it. + /// </summary> + /// <param name="groupList">Group to be added.</param> + /// <param name="beforeCell">Before cell.</param> + void AddGroupItem(GroupList groupList, Cell beforeCell = null) + { + Cell groupCell = groupList.HeaderContent; + CellRenderer groupRenderer = GetCellRenderer(groupCell, true); + ItemContext groupItemContext = new ItemContext(); + groupItemContext.Cell = groupCell; + groupItemContext.Renderer = groupRenderer; + + if (beforeCell != null) + { + GenListItem beforeItem = GetItemContext(beforeCell)?.Item; + groupItemContext.Item = InsertBefore(groupRenderer.Class, groupItemContext, beforeItem, GenListItemType.Group); + } + else + { + groupItemContext.Item = Append(groupRenderer.Class, groupItemContext, GenListItemType.Group); + } + + groupItemContext.Item.SelectionMode = GenListSelectionMode.None; + groupItemContext.IsGroupItem = true; + + groupItemContext.ListOfSubItems = groupList; + groupItemContext.Item.Deleted += ItemDeletedHandler; + _itemContextList.Add(groupItemContext); + } + + /// <summary> + /// Adds the item. + /// </summary> + /// <param name="cell">Cell to be added.</param> + /// <param name="groupCell">Group to which the new item should belong.</param> + /// <remark>If the value of <c>groupCell</c> is not null, the new item will be put into the requested group. </remark> + /// <param name="beforeCell">The cell before which the new item should be placed.</param> + /// <remarks> If the value of <c>beforeCell</c> is not null, the new item will be placed just before the requested cell. </remarks> + void AddItem(Cell cell, Cell groupCell = null, Cell beforeCell = null) + { + CellRenderer renderer = GetCellRenderer(cell); + GenListItem parentItem = null; + + ItemContext itemContext = new ItemContext(); + itemContext.Cell = cell; + itemContext.Renderer = renderer; + + if (IsGroupingEnabled && groupCell != null) + { + var groupContext = GetItemContext(groupCell); + itemContext.ListOfSubItems = groupContext.ListOfSubItems; + parentItem = groupContext.Item; + } + + if (beforeCell != null) + { + GenListItem beforeItem = GetItemContext(beforeCell)?.Item; + itemContext.Item = InsertBefore(renderer.Class, itemContext, beforeItem, GenListItemType.Normal, parentItem); + } + else + { + itemContext.Item = Append(renderer.Class, itemContext, GenListItemType.Normal, parentItem); + } + + itemContext.Item.SelectionMode = GenListSelectionMode.Always; + + cell.PropertyChanged += OnCellPropertyChanged; + (cell as ICellController).ForceUpdateSizeRequested += OnForceUpdateSizeRequested; + itemContext.Item.Deleted += ItemDeletedHandler; + _itemContextList.Add(itemContext); + } + + /// <summary> + /// Handles item deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void ItemDeletedHandler(object sender, EventArgs e) + { + ItemContext itemContext = (sender as GenListItem).Data as ItemContext; + if (itemContext.Cell != null) + { + itemContext.Cell.PropertyChanged -= OnCellPropertyChanged; + (itemContext.Cell as ICellController).ForceUpdateSizeRequested -= OnForceUpdateSizeRequested; + } + _itemContextList.Remove(itemContext); + } + + /// <summary> + /// Invoked whenever the properties of data model change. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">PropertyChangedEventArgs.</param> + /// <remarks> + /// The purpose of this method is to propagate these changes to the presentation layer. + /// </remarks> + void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var cell = sender as Cell; + var context = GetItemContext(cell); + context.Renderer.SendCellPropertyChanged(cell, context.Item, e.PropertyName); + } + + void OnForceUpdateSizeRequested(object sender, EventArgs e) + { + var cell = sender as Cell; + var itemContext = GetItemContext(cell); + if (itemContext.Item != null) + itemContext.Item.Update(); + } + + /// <summary> + /// Gets the item class used for header and footer cells. + /// </summary> + /// <returns>The header and footer item class.</returns> + GenItemClass GetHeaderFooterItemClass() + { + if (_headerFooterItemClass == null) + { + _headerFooterItemClass = new GenItemClass("full") + { + GetContentHandler = (data, part) => + { + VisualElement element = data as VisualElement; + var renderer = Platform.GetOrCreateRenderer(element); + + if (element.MinimumHeightRequest == -1) + { + SizeRequest request = element.Measure(double.PositiveInfinity, double.PositiveInfinity); + renderer.NativeView.MinimumHeight = (int)request.Request.Height; + } + else + { + renderer.NativeView.MinimumHeight = (int)element.MinimumHeightRequest; + } + + return renderer.NativeView; + } + }; + } + return _headerFooterItemClass; + } + } +} |