using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Chernobyl.Collections.Generic.Event;
using Chernobyl.Dependency;
using Chernobyl.Event;
using Chernobyl.Graphics;
using Chernobyl.Graphics.Drawing;
using Chernobyl.Graphics.Texture;
using Chernobyl.Interface.Input;
using Chernobyl.Interface.Tool;
using Chernobyl.Interface.Utility;
using Chernobyl.Mathematics.Collision;
using Chernobyl.Mathematics.Geometry;
using Chernobyl.Mathematics.Mechanics;
using Chernobyl.Mathematics.Movement;
namespace Chernobyl.Interface.Collections
{
///
/// An interface that implements and renders s.
///
/// Note that the doesn't store information
/// about indices as it cannot guarantee that the items stored within the
/// are indexed or are ordered to being with.
/// You can do this yourself using the methods available to the
/// and but you
/// should be certain that the contained within
/// this at least has an order that can
/// be indexed.
///
/// Performance Tip: If you do not require items in the list to be selected
/// then you can improve performance by turning off selection of items within
/// the list using a of
/// .
/// Doing this will stop the list from setting up the infrastructure necessary
/// to allow objects to be selected.
///
/// The type that is to be held in the list.
public class EnumerableListView : MatrixTransform, IEnumerable
{
///
/// Initializes a new instance of the
/// class.
///
/// The instance that gives out services for use
/// by this type and takes services from this type for use by other systems.
/// The enumerable that should be viewable in
/// the interface list
public EnumerableListView(IEventCollection services, IEnumerable enumerable)
: this(services, enumerable, null)
{ }
///
/// Initializes a new instance of the
/// class.
///
/// The instance that gives out services for use
/// by this type and takes services from this type for use by other systems.
/// The enumerable that should be viewable in
/// the interface list
/// The method that determines when items in
/// the list can be selected or null if the default
/// should be used (The default value is
/// given by the field ). Several
/// methods are available in the
/// struct
/// or you can use your own. For performance reasons, if you don't need
/// an item to be selected, you should use
/// .
public EnumerableListView(IEventCollection services, IEnumerable enumerable, SelectionModeFunc selectionMode)
: this(services, enumerable, selectionMode, null)
{ }
///
/// Initializes a new instance of the
/// class.
///
/// The instance that gives out services for use
/// by this type and takes services from this type for use by other systems.
/// The enumerable that should be viewable in
/// the interface list
/// The that will be used to
/// interact with the list or null if
/// should be used.
/// The method that determines when items in
/// the list can be selected or null if the default
/// should be used (The default value is
/// given by the field ). Several
/// methods are available in the
/// struct
/// or you can use your own. For performance reasons, if you don't need
/// an item to be selected, you should use
/// .
public EnumerableListView(IEventCollection services, IEnumerable enumerable, SelectionModeFunc selectionMode, IButton button)
{
Services = services;
SelectionMode = selectionMode ?? SelectionModeDefault;
SelectionItems = new List();
_selectedItems = Enumerable.Empty();
TheEnumerable = enumerable;
Layout = new Layout(Axis.Y);
Button = button;
MakeParentChild(this, Layout);
services.Inject>(this);
}
///
/// The method that determines when an item in the list can be selected.
///
/// The item that whose as selected or not selected is
/// to be determined by the method.
/// The in which
/// is to be selected.
/// True if the item can be selected, false if otherwise.
public delegate bool SelectionModeFunc(T item, EnumerableListView listView);
///
/// The method that determines when items in the list can be selected.
/// Several default methods are available in the
/// struct
/// or you can use your own. The default value of this property is given
/// by the field .
///
public SelectionModeFunc SelectionMode
{
get { return _selectionMode; }
set
{
_selectionMode = value;
Refresh();
}
}
///
/// The default value of the property . The
/// value of this field is .
///
public static readonly SelectionModeFunc SelectionModeDefault = SelectionMode.Single;
///
/// The items that have been selected in the list or an empty
/// if nothing is selected.
///
/// Thrown if the
/// value set on this property contains items
/// that are not contained within this IEnumerable{T}.
public IEnumerable SelectedItems
{
get
{
return _selectedItems;
}
set
{
if (value.All(item => TheEnumerable.Contains(item)) == false)
throw new ArgumentException("One or more of the items in the " +
"IEnumerable set on this property are not contained " +
"within this IEnumerable. Please ensure the items you've " +
"set to be selected are actually contained with this list.", "value");
if (SelectedItemsChanged != null)
{
IEnumerable oldItems = _selectedItems;
_selectedItems = value;
SelectedItemsChanged(this, new ValueChangedEventArgs>(oldItems, SelectedItems));
}
else
_selectedItems = value;
// go through all of the selection items and inform them that they
// are selected. Grab the number of items selected while we are
// at it
int count = 0;
foreach (ItemSelectionContainer itemSelectionContainer in SelectionItems.Where(item => _selectedItems.Contains(item.Item)))
{
++count;
itemSelectionContainer.IsSelected = true;
}
// if no items are selected than we need to inform all selection
// items that they are no longer selected
if(count == 0)
{
foreach (ItemSelectionContainer itemSelectionContainer in SelectionItems.Where(item => item.IsSelected))
itemSelectionContainer.IsSelected = false;
}
}
}
///
/// An event that is raised right after a new item or items have been
/// selected.
///
public event EventHandler>> SelectedItemsChanged;
///
/// Updates the view so that the items viewed in the list correlate to
/// the items in the at this moment.
///
public void Refresh()
{
if (_standardInterfaceFactory != null)
{
Layout.Clear();
SelectionItems.Clear();
if (TheEnumerable.Any() == true)
{
if(_parentDrawableForItemViews != null)
{
_parentDrawableForItemViews.DrawableChildren.Clear();
foreach (T item in TheEnumerable)
{
IDrawableTransformCollidable2D view = _standardInterfaceFactory(item);
Layout.Add(view);
view.DrawableParent = _parentDrawableForItemViews;
// don't need to do any setup of selection properties if nothing
// can be selected in the first place
if (SelectionMode != SelectionMode.None)
{
if (_parentDrawableForSelectionBackground != null && _cursor != null)
{
ItemSelectionContainer itemContainer = new ItemSelectionContainer(Services, this, item, _cursor, Button, view, _parentDrawableForSelectionBackground);
SelectionItems.Add(itemContainer);
}
else
{
Interface.Trace.TraceEvent(TraceEventType.Warning, 0, "Unable to refresh " +
"the selected items of the EnumerableListView \"" + Name + "\" because the \"" +
typeof(Scene).FullName + "\" and \"" + typeof(ICursor).FullName + "\" services have not been set on this instance " +
"or the \"" + typeof(Scene).FullName + "\" has been set on this instance but it did not contain a valid IDrawable " +
"for drawing 2D (see the Scene.Draw2D property).");
}
}
}
}
else
{
Interface.Trace.TraceEvent(TraceEventType.Warning, 0, "Unable to refresh " +
"the EnumerableListView \"" + Name + "\" because the \"" + typeof(Scene).FullName +
"\" service has not been set on this instance or it has and it did not " +
"contain a valid IDrawable for drawing 3D (see Scene.Draw3D property).");
}
}
}
else
{
Interface.Trace.TraceEvent(TraceEventType.Warning, 0, "Unable to refresh " +
"the EnumerableListView \"" + Name + "\" because the \"" +
typeof(IStandardInterfaceFactoryCollection).FullName + "\" service has not " +
"been set on this instance.");
}
}
///
/// An event handler that can be assigned to an event. This method performs
/// the same task as (i.e. updates the view so
/// that the items viewed in the list correlate to the items in the
/// at this moment).
///
/// The sender.
/// The instance containing the event data.
public void Refresh(object sender, EventArgs e)
{
Refresh();
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through
/// the collection.
///
public IEnumerator GetEnumerator()
{
return TheEnumerable.GetEnumerator();
}
///
/// Returns an enumerator that iterates through a collection.
///
///
/// An object that can be used to iterate
/// through the collection.
///
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
///
/// The that the text should be rendered in.
///
[Inject]
public Scene Scene
{
set
{
if (_parentDrawableForSelectionBackground == null)
_parentDrawableForSelectionBackground = new Drawable();
if (_parentDrawableForItemViews == null)
_parentDrawableForItemViews = new Drawable();
Refresh();
}
}
///
/// The class that holds interface
/// factory collections.
///
[Inject]
public IInterfaceFactoryCollectionServices FactoryCollectionServices
{
set
{
StandardInterfaceFactoryCollection = value.OfType().First();
}
}
///
/// The cursor to use when selecting items in the list.
///
[Inject]
public ICursor Cursor
{
set
{
_cursor = value;
if (Button == null)
Button = _cursor.FirstButton;
Refresh();
}
}
///
/// The collection that holds the standard interface factorys. The
/// appropriate interface factory (based on the type held by this list
/// view) will be used to create the item views for each of the items in
/// the list.
///
IStandardInterfaceFactoryCollection StandardInterfaceFactoryCollection
{
set
{
value.TryGetValue(typeof(T), out _standardInterfaceFactory);
if (_standardInterfaceFactory == null)
{
IEnumerator baseTypesEnumerator = Chernobyl.Reflection.Utility.GetBaseTypes().GetEnumerator();
while (_standardInterfaceFactory == null && baseTypesEnumerator.MoveNext() == true)
value.TryGetValue(baseTypesEnumerator.Current, out _standardInterfaceFactory);
baseTypesEnumerator.Dispose();
}
if (_standardInterfaceFactory == null)
throw new Exception("Unable to create the list view. Failed " +
"to retrieve an interface factory for the type \"" +
typeof(T).FullName + "\". Please ensure the proper \"" +
typeof(IStandardInterfaceFactoryCollection).FullName +
"\" was passed as a service to this EnumerableListView " +
"and that it contains a proper interface factory for the type " +
"contained within the list.");
Refresh();
}
}
///
/// The list of items that can be selected in the list.
///
List SelectionItems { get; set; }
///
/// The button to use for selecting items in the list.
///
IButton Button { get; set; }
///
/// "lays out" the items in the list (provides the ordering and separation
/// of items in the list).
///
Layout Layout { get; set; }
///
/// The enumerable that should be viewable in the interface list.
///
IEnumerable TheEnumerable { get; set; }
///
/// The instance that gives out services for use by this type and takes
/// services from this type for use by other systems.
///
IEventCollection Services { get; set; }
///
/// The factory that creates standard interface views.
///
InterfaceFactory _standardInterfaceFactory;
///
/// The that is to become the parent of the
/// item views in the list.
///
IDrawable _parentDrawableForItemViews;
///
/// The that is to become the parent of the
/// instance that will be the items "selection
/// background".
///
IDrawable _parentDrawableForSelectionBackground;
///
/// The backing field to .
///
SelectionModeFunc _selectionMode;
///
/// The backing field to .
///
IEnumerable _selectedItems;
///
/// The backing field to .
///
ICursor _cursor;
///
/// Holds the data necessary to perform selection on items within the list.
/// This class also sets up and performs the selection on the items.
///
class ItemSelectionContainer
{
///
/// Initializes a new instance of the class.
///
/// The instance that gives out services for
/// use by this type and takes services from this type for use by
/// other systems.
/// The list view that contains the item in
/// question.
/// The item that can be selected in the
///
/// The cursor that is used to select the item.
/// The button that is used to select the item.
/// The view that is drawn on screen to represent
/// the .
/// The
/// that is to become the parent of the
/// instance that will be the items "selection
/// background".
public ItemSelectionContainer(IEventCollection services, EnumerableListView listView, T item, ICursor cursor, IButton button, ICollidable2D view,
IDrawable parentDrawableForSelectionBackground)
{
IsSelected = false;
Services = services;
ListView = listView;
Item = item;
ParentDrawableForSelectionBackground = parentDrawableForSelectionBackground;
// create the background to make the item look like it is selected
ShapeCollection viewCollection = new ShapeCollection(new IShape[] {view});
SelectionBackground = new Background(Services, new Texture2D(Services, ColorRgba.Blue), viewCollection);
// Allow the item to be clicked using the ClickableRegion extension.
// We use a WeakEventHandler to allow this ItemSelectionContainer
// to be garbage collected. We pass in a null unregister method
// to WeakEventHandler because a lambda to unregister the WeakEventHandler
// would create a strong reference to ClickableRegion and prevent it
// from getting garbage collected.
ClickableRegion = new ClickableRegion(services, null, view);
ClickableRegion.PressedDown += new WeakEventHandler(CursorPressedDown, null);
}
///
/// True if the object is selected, false if otherwise.
///
public bool IsSelected
{
get { return _isSelected; }
set
{
if(value != _isSelected)
{
if(value == true && ListView.SelectionMode(Item, ListView) == true)
{
_isSelected = true;
}
else
{
_isSelected = false;
ParentDrawableForSelectionBackground.DrawableChildren.Remove(SelectionBackground);
}
}
}
}
///
/// The item that can be selected in the .
///
public T Item { get; private set; }
///
/// An event handler that is invoked when the cursor clicks on an
/// item within the list.
///
/// The sender of the event.
/// The instance
/// containing the event data.
void CursorPressedDown(object sender, PointerEventArgs e)
{
IsSelected = !IsSelected;
if (IsSelected == true)
{
// add the item to the selected items in the list view
ListView.SelectedItems = ListView.SelectedItems.Concat(Item.ToEnumerable());
}
else
{
// remove the item from the selected items in the list view
ListView.SelectedItems = ListView.SelectedItems.Except(Item.ToEnumerable());
}
}
///
/// The list view that contains the item in question.
///
EnumerableListView ListView { get; set; }
///
/// The that is to become the parent of the
/// instance that will be the items "selection
/// background".
///
IDrawable ParentDrawableForSelectionBackground { get; set; }
///
/// The instance that gives out services for use by this type and
/// takes services from this type for use by other systems.
///
IEventCollection Services { get; set; }
///
/// The background that is applied to the item to show that it is
/// selected.
///
Background SelectionBackground { get; set; }
///
/// Used to allow an item in the list to be clicked on.
///
ClickableRegion ClickableRegion { get; set; }
///
/// The backing field to .
///
bool _isSelected;
};
}
///
/// A holder class for several predefined methods for selecting an item from
/// an
///
/// The type of the items contained within the list.
public static class SelectionMode
{
///
/// A method to use when you don't want items to be selected.
///
/// The item that whose as selected or not selected is
/// to be determined by the method.
/// The in which
/// is to be selected.
/// Always returns false.
public static bool None(T item, EnumerableListView listView)
{
return false;
}
///
/// A method to use when you only want a single item at a time to be
/// selected.
///
/// The item that whose as selected or not selected is
/// to be determined by the method.
/// The in which
/// is to be selected.
/// Returns true if no item has been selected, false if otherwise.
public static bool Single(T item, EnumerableListView listView)
{
if (listView.SelectedItems.Count() != 0)
listView.SelectedItems = Enumerable.Empty();
return true;
}
///
/// A method to use if you want to allow multiple items to be selected.
///
/// The item that whose as selected or not selected is
/// to be determined by the method.
/// The in which
/// is to be selected.
/// True if the item can be selected, false if otherwise.
public static bool Multiple(T item, EnumerableListView listView)
{
return true;
}
}
}