using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Xamarin.Forms.Internals;
using ElmSharp;
namespace Xamarin.Forms.Platform.Tizen.Native
{
///
/// Type alias which identifies list of cells whose data model was transformed by Xamarin.
///
using GroupList = TemplatedItemsList, Cell>;
///
/// Native ListView implementation for Xamarin renderer
///
///
/// 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.
///
public class ListView : GenList
{
///
/// ItemContext helper class. This represents the association between Xamarin.Forms.Cell and
/// native elements. It also stores useful context for them.
///
public class ItemContext
{
public ItemContext()
{
Item = null;
Cell = null;
Renderer = null;
ListOfSubItems = null;
}
public GenItem Item;
public Cell Cell;
public bool IsGroupItem;
public CellRenderer Renderer;
internal TemplatedItemsList, Cell> ListOfSubItems;
}
///
/// The item context list for each added element.
///
readonly List _itemContextList = new List();
///
/// Registered cell handlers.
///
protected readonly IDictionary _cellRendererCache = new Dictionary();
///
/// Registered group handlers.
///
protected readonly IDictionary _groupCellRendererCache = new Dictionary();
///
/// The header context.
///
ItemContext _headerContext;
///
/// The header element.
///
VisualElement _headerElement;
///
/// The footer context.
///
ItemContext _footerContext;
///
/// The footer element.
///
VisualElement _footerElement;
///
/// The item class for header and footer.
///
GenItemClass _headerFooterItemClass = null;
///
/// Gets or sets a value indicating whether this instance has grouping enabled.
///
/// true if this instance has grouping enabled.
public bool IsGroupingEnabled { get; set; }
///
/// Constructor of ListView native control.
///
/// ElmSharp object which is parent of particular list view
public ListView(EvasObject parent)
: base(parent)
{
ItemRealized += OnItemAppear;
ItemUnrealized += OnItemDisappear;
}
///
/// Gets the item context based on Cell item.
///
/// The item context.
/// Cell for which context should be found.
internal ItemContext GetItemContext(Cell cell)
{
if (cell == null)
{
return null;
}
else
{
return _itemContextList.Find(X => X.Cell == cell);
}
}
///
/// Sets the HasUnevenRows property.
///
/// If true, the list will allow uneven sizes for its rows.
public void SetHasUnevenRows(bool hasUnevenRows)
{
Homogeneous = !hasUnevenRows;
UpdateRealizedItems();
}
///
/// Adds elements to the list and defines its presentation based on Cell type.
///
/// IEnumerable on Cell collection.
/// 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.
///
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);
}
}
}
///
/// Deletes all items from a given group.
///
/// Group of items to be deleted.
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();
}
}
///
/// Adds items to the group.
///
/// Group to which elements will be added.
/// New list items to be added.
/// A reference to the Cell already existing in a ListView.
/// Newly added cells will be put just before this cell.
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);
}
}
}
///
/// Removes the specified cells.
///
/// Cells to be removed.
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();
}
}
}
///
/// Scrolls the list to a specified cell.
///
///
/// 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 position parameter.
///
/// Cell which will be displayed after scrolling .
/// This will defines scroll to behavior based on ScrollToPosition values.
/// If true, scrolling will be animated. Otherwise the cell will be moved instantaneously.
public void ApplyScrollTo(Cell cell, ScrollToPosition position, bool animated)
{
GenListItem item = GetItemContext(cell)?.Item as GenListItem;
if (item != null)
this.ScrollTo(item, position.ToNative(), animated);
}
///
/// Selects the specified cell.
///
/// Cell to be selected.
public void ApplySelectedItem(Cell cell)
{
GenListItem item = GetItemContext(cell)?.Item as GenListItem;
if (item != null)
item.IsSelected = true;
}
///
/// Sets the header.
///
/// Header of the list.
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 = GenItemSelectionMode.None;
_headerContext.Item.Deleted += HeaderDeletedHandler;
_itemContextList.Insert(0, _headerContext);
}
}
///
/// Sets the footer.
///
/// Footer of the list.
public void SetFooter(VisualElement footer)
{
if (footer == null)
{
if (HasFooter())
{
RemoveFooter();
}
return;
}
GenItemClass footerTemplate = GetHeaderFooterItemClass();
_footerElement = footer;
if (HasFooter())
{
(_footerContext.Item as GenListItem).UpdateItemClass(footerTemplate, footer);
}
else
{
_footerContext = new ItemContext();
_footerContext.Item = Append(footerTemplate, footer);
_footerContext.Item.SelectionMode = GenItemSelectionMode.None;
_footerContext.Item.Deleted += FooterDeletedHandler;
_itemContextList.Add(_footerContext);
}
}
///
/// Removes the header.
///
public void RemoveHeader()
{
_itemContextList.Remove(_headerContext);
_headerContext?.Item?.Delete();
_headerContext = null;
_headerElement = null;
}
///
/// Removes the footer.
///
public void RemoveFooter()
{
_itemContextList.Remove(_footerContext);
_footerContext?.Item?.Delete();
_footerContext = null;
_footerElement = null;
}
///
/// Determines whether this instance has a header.
///
/// true if the header is present.
public bool HasHeader()
{
return _headerContext != null;
}
///
/// Determines whether this instance has a footer.
///
/// true if the footer is present.
public bool HasFooter()
{
return _footerContext != null;
}
///
/// Gets the header.
///
/// The header.
public VisualElement GetHeader()
{
return _headerElement;
}
///
/// Gets the footer.
///
/// The footer.
public VisualElement GetFooter()
{
return _footerElement;
}
///
/// Handles the header deleted event.
///
/// Sender of the event.
/// Empty argument.
void HeaderDeletedHandler(object sender, EventArgs e)
{
_itemContextList.Remove(_headerContext);
_headerContext = null;
}
///
/// Handles the footer deleted event.
///
/// Sender of the event.
/// Empty argument.
void FooterDeletedHandler(object sender, EventArgs e)
{
_itemContextList.Remove(_footerContext);
_footerContext = null;
}
///
/// Called every time an object gets realized.
///
/// Sender of the event.
/// GenListItemEventArgs.
void OnItemAppear(object sender, GenListItemEventArgs evt)
{
ItemContext itemContext = (evt.Item.Data as ItemContext);
if (itemContext != null && itemContext.Cell != null)
{
(itemContext.Cell as ICellController).SendAppearing();
}
}
///
/// Called every time an object gets unrealized.
///
/// Sender of the event.
/// GenListItemEventArgs.
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);
}
}
///
/// A convenience shorthand method for derivate classes.
///
/// Cell to be added.
protected void AddCell(Cell cell)
{
AddItem(cell);
}
///
/// Gets the cell renderer for given cell type.
///
/// The cell handler.
/// Cell to be added.
/// If true, then group handlers will be included in the lookup as well.
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(type);
if (renderer == null)
{
Log.Error("Cell type is not handled: {0}", cell.GetType());
throw new ArgumentNullException("Unsupported cell type");
}
return cache[type] = renderer;
}
///
/// 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.
///
/// Group to be added.
/// Before cell.
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;
groupItemContext.IsGroupItem = true;
groupItemContext.ListOfSubItems = groupList;
_itemContextList.Add(groupItemContext);
if (beforeCell != null)
{
GenListItem beforeItem = GetItemContext(beforeCell)?.Item as GenListItem;
groupItemContext.Item = InsertBefore(groupRenderer.Class, groupItemContext, beforeItem, GenListItemType.Group);
}
else
{
groupItemContext.Item = Append(groupRenderer.Class, groupItemContext, GenListItemType.Group);
}
groupItemContext.Item.SelectionMode = GenItemSelectionMode.None;
groupItemContext.Item.IsEnabled = groupCell.IsEnabled;
groupItemContext.Item.Deleted += ItemDeletedHandler;
}
///
/// Adds the item.
///
/// Cell to be added.
/// Group to which the new item should belong.
/// If the value of groupCell is not null, the new item will be put into the requested group.
/// The cell before which the new item should be placed.
/// If the value of beforeCell is not null, the new item will be placed just before the requested cell.
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;
_itemContextList.Add(itemContext);
if (IsGroupingEnabled && groupCell != null)
{
var groupContext = GetItemContext(groupCell);
itemContext.ListOfSubItems = groupContext.ListOfSubItems;
parentItem = groupContext.Item as GenListItem;
}
if (beforeCell != null)
{
GenListItem beforeItem = GetItemContext(beforeCell)?.Item as GenListItem;
itemContext.Item = InsertBefore(renderer.Class, itemContext, beforeItem, GenListItemType.Normal, parentItem);
}
else
{
itemContext.Item = Append(renderer.Class, itemContext, GenListItemType.Normal, parentItem);
}
itemContext.Item.SelectionMode = GenItemSelectionMode.Always;
itemContext.Item.IsEnabled = cell.IsEnabled;
itemContext.Item.Deleted += ItemDeletedHandler;
cell.PropertyChanged += OnCellPropertyChanged;
(cell as ICellController).ForceUpdateSizeRequested += OnForceUpdateSizeRequested;
}
///
/// Handles item deleted event.
///
/// Sender of the event.
/// Empty argument.
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);
}
///
/// Invoked whenever the properties of data model change.
///
/// Sender of the event.
/// PropertyChangedEventArgs.
///
/// The purpose of this method is to propagate these changes to the presentation layer.
///
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();
}
///
/// Gets the item class used for header and footer cells.
///
/// The header and footer item class.
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 = Forms.ConvertToScaledPixel(request.Request.Height);
}
else
{
renderer.NativeView.MinimumHeight = Forms.ConvertToScaledPixel(element.MinimumHeightRequest);
}
(renderer as LayoutRenderer)?.RegisterOnLayoutUpdated();
return renderer.NativeView;
}
};
}
return _headerFooterItemClass;
}
}
}