//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System.Collections.Generic;
namespace BansheeEngine
{
/** @addtogroup GUI_Engine
* @{
*/
///
/// GUI element that can efficiently display a list of entries that share the same height. This element is mostly an
/// optimization as only visible entries have actual GUI elements, as opposed to just adding GUI elements directly
/// in a vertical GUI layout. This allows the list view to have thousands of elements with little performance impact.
///
/// Contains shared functionality used by all instances of .
///
/// >Type used for storing the data for all list entries.
public abstract class GUIListViewBase
where TData : GUIListViewData
{
// TODO - Only fixed size is supported. It should be nice if this object could just be placed in layout like any
// other GUI element. Would likely need some kind of a way to get notified when parent layout changes.
// (Possibly add a callback to GUIPanel when updateLayout is called?)
protected List entries = new List();
protected GUIScrollArea scrollArea;
protected GUILabel topPadding;
protected GUILabel bottomPadding;
protected int width;
protected int height;
protected int entryHeight;
protected float scrollPct;
protected bool scrollToLatest;
protected int totalHeight;
protected internal bool contentsDirty = true;
///
/// Total number of entries in the list.
///
public int NumEntries
{
get { return entries.Count; }
}
///
/// Height of a single entry in the list, in pixels.
///
public int EntryHeight
{
get { return entryHeight; }
set { entryHeight = value; }
}
///
/// Primary GUI scroll area that all entries are contained within.
///
internal GUIScrollArea ScrollArea
{
get { return scrollArea; }
}
///
/// Creates a new empty list view.
///
/// Width of the list view, in pixels.
/// Height of the list view, in pixels.
/// Height of a single element in the list, in pixels.
/// GUI layout into which the list view will be placed into.
protected GUIListViewBase(int width, int height, int entryHeight, GUILayout layout)
{
scrollArea = new GUIScrollArea(ScrollBarType.ShowIfDoesntFit, ScrollBarType.NeverShow,
GUIOption.FixedWidth(width), GUIOption.FixedHeight(height));
layout.AddElement(scrollArea);
topPadding = new GUILabel(new LocString());
bottomPadding = new GUILabel(new LocString());
scrollArea.Layout.AddElement(topPadding);
scrollArea.Layout.AddElement(bottomPadding);
this.width = width;
this.height = height;
this.entryHeight = entryHeight;
}
///
/// Adds a new entry to the end of the list.
///
/// Data of the entry to add.
public void AddEntry(TData data)
{
entries.Add(data);
contentsDirty = true;
}
///
/// Removes an entry from the specified index. If the index is out of range nothing happens.
///
/// Sequential index of the element to remove from the list.
public void RemoveEntry(int index)
{
if (index >= 0 && index < entries.Count)
{
entries.RemoveAt(index);
contentsDirty = true;
}
}
///
/// Removes all entries from the list.
///
public void Clear()
{
entries.Clear();
contentsDirty = true;
}
///
/// Finds an index of the specified entry in the list.
///
/// Data of the entry to search for.
/// Index of the entry if found, -1 otherwise.
public int FindEntry(TData data)
{
return entries.FindIndex(x => x.Equals(data));
}
///
/// Adds a new entry at the specified index. If the index is out of range the entry is added at the end of the list.
///
/// Sequential index at which to insert the entry.
/// Data of the entry to insert.
public void InsertEntry(int index, TData data)
{
if (index >= 0 && index <= entries.Count)
entries.Insert(index, data);
else
entries.Add(data);
contentsDirty = true;
}
///
/// Changes the size of the list view.
///
/// Width in pixels.
/// Height in pixels.
public void SetSize(int width, int height)
{
if (width != this.width || height != this.height)
{
this.width = width;
this.height = height;
Rect2I bounds = scrollArea.Bounds;
bounds.width = width;
bounds.height = height;
scrollArea.Bounds = bounds;
}
}
///
/// Updates the visuals of the list view. Should be called once per frame.
///
public abstract void Update();
}
///
/// GUI element that can efficiently display a list of entries that share the same height. This element is mostly an
/// optimization as only visible entries have actual GUI elements, as opposed to just adding GUI elements directly
/// in a vertical GUI layout. This allows the list view to have thousands of elements with little performance impact.
///
/// Type used for creating and updating the GUI elements of the visible entries.
/// Type used for storing the data for all list entries.
public class GUIListView : GUIListViewBase
where TEntry : GUIListViewEntry, new()
where TData : GUIListViewData
{
private List visibleEntries = new List();
///
/// Creates a new empty list view.
///
/// Width of the list view, in pixels.
/// Height of the list view, in pixels.
/// Height of a single element in the list, in pixels.
/// GUI layout into which the list view will be placed into.
public GUIListView(int width, int height, int entryHeight, GUILayout layout)
:base(width, height, entryHeight, layout)
{ }
///
public override void Update()
{
int numVisibleEntries = MathEx.CeilToInt(height / (float)entryHeight) + 1;
numVisibleEntries = MathEx.Min(numVisibleEntries, entries.Count);
while (visibleEntries.Count < numVisibleEntries)
{
TEntry newEntry = new TEntry();
newEntry.Initialize(this);
newEntry.panel.SetHeight(entryHeight);
visibleEntries.Add(newEntry);
contentsDirty = true;
}
while (numVisibleEntries < visibleEntries.Count)
{
int lastIdx = visibleEntries.Count - 1;
visibleEntries[lastIdx].Destroy();
visibleEntries.RemoveAt(lastIdx);
contentsDirty = true;
}
if (scrollPct != scrollArea.VerticalScroll)
{
scrollPct = scrollArea.VerticalScroll;
contentsDirty = true;
}
if (contentsDirty)
{
int newHeight = entries.Count * entryHeight;
if (scrollToLatest)
{
if (totalHeight > height && scrollPct < 1.0f)
scrollToLatest = false;
}
else
{
if (totalHeight <= height || scrollPct >= 1.0f)
scrollToLatest = true;
}
totalHeight = newHeight;
int maxScrollOffset = MathEx.Max(0, totalHeight - height - 1);
float newScrollPct;
if (!scrollToLatest)
{
// Calculate the new scroll pct (which will be active after we change the top/bottom padding element
// sizes). If we use the existing scroll pct instead then the elements will lag one frame behind, which
// can be very noticeable on quickly updating lists.
newScrollPct = (scrollPct*scrollArea.Layout.Bounds.height)/totalHeight;
}
else
newScrollPct = 1.0f;
int startPos = MathEx.FloorToInt(newScrollPct * maxScrollOffset);
int startIndex = MathEx.FloorToInt(startPos / (float)entryHeight);
// Check if we're at the list bottom and the extra element is out of bounds
if ((startIndex + visibleEntries.Count) > entries.Count)
startIndex--; // Keep the extra element at the top always
topPadding.SetHeight(startIndex * entryHeight);
for (int i = 0; i < visibleEntries.Count; i++)
visibleEntries[i].UpdateContents(startIndex + i, entries[startIndex + i]);
int bottomPosition = MathEx.Min(totalHeight, (startIndex + visibleEntries.Count) * entryHeight);
bottomPadding.SetHeight(totalHeight - bottomPosition);
if (scrollToLatest)
{
if (newHeight <= height)
scrollArea.VerticalScroll = 0.0f;
else
scrollArea.VerticalScroll = 1.0f;
}
contentsDirty = false;
}
}
}
///
/// Base class that contains data for individual entries used in .
///
public class GUIListViewData
{
}
///
/// Base class that displays GUI elements for visible entries used in .
///
/// Type of object that contains data used for initializing the GUI elements.
public abstract class GUIListViewEntry
where TData : GUIListViewData
{
private GUIListViewBase parent;
internal GUIPanel panel;
internal GUILayoutY layout;
protected GUILayout Layout { get { return layout; } }
///
/// Initializes the GUI elements for the entry.
///
/// Scroll area into whose layout to insert the GUI elements.
internal void Initialize(GUIListViewBase parent)
{
this.parent = parent;
GUIScrollArea scrollArea = parent.ScrollArea;
int numElements = scrollArea.Layout.ChildCount;
// Last panel is always the padding panel, so keep it there
panel = scrollArea.Layout.InsertPanel(numElements - 1);
layout = panel.AddLayoutY();
BuildGUI();
}
///
/// Destoys the GUI elements for the entry.
///
internal void Destroy()
{
panel.Destroy();
}
///
/// Causes all visible entries in the parent list to be updated.
///
protected void RefreshEntries()
{
parent.contentsDirty = true;
}
///
/// Allows child classes to create GUI elements required by their entry specialization.
///
public abstract void BuildGUI();
///
/// Allows child classes to update GUI element(s) with new contents.
///
/// Sequential index of the entry in the list.
/// Data of the entry to display.
public abstract void UpdateContents(int index, TData data);
}
/** @} */
}