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; } } }