| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- using System.Collections.Generic;
- namespace BansheeEngine
- {
- /// <summary>
- /// 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 <see cref="GUIListView{TEntry,TData}"/>.
- /// </summary>
- /// <typeparam name="TData">>Type used for storing the data for all list entries.</typeparam>
- public abstract class GUIListViewBase<TData>
- 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<TData> entries = new List<TData>();
- protected GUIScrollArea scrollArea;
- protected GUILabel topPadding;
- protected GUILabel bottomPadding;
- protected int width;
- protected int height;
- protected int entryHeight;
- protected float scrollPct = 0.0f;
- protected bool scrollToLatest = true;
- protected internal bool contentsDirty = true;
- /// <summary>
- /// Total number of entries in the list.
- /// </summary>
- public int NumEntries
- {
- get { return entries.Count; }
- }
- /// <summary>
- /// Height of a single entry in the list, in pixels.
- /// </summary>
- public int EntryHeight
- {
- get { return entryHeight; }
- set { entryHeight = value; }
- }
- /// <summary>
- /// Primary GUI scroll area that all entries are contained within.
- /// </summary>
- internal GUIScrollArea ScrollArea
- {
- get { return scrollArea; }
- }
- /// <summary>
- /// Creates a new empty list view.
- /// </summary>
- /// <param name="width">Width of the list view, in pixels.</param>
- /// <param name="height">Height of the list view, in pixels.</param>
- /// <param name="entryHeight">Height of a single element in the list, in pixels.</param>
- /// <param name="layout">GUI layout into which the list view will be placed into.</param>
- 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;
- }
- /// <summary>
- /// Adds a new entry to the end of the list.
- /// </summary>
- /// <param name="data">Data of the entry to add.</param>
- public void AddEntry(TData data)
- {
- entries.Add(data);
- contentsDirty = true;
- }
- /// <summary>
- /// Removes an entry from the specified index. If the index is out of range nothing happens.
- /// </summary>
- /// <param name="index">Sequential index of the element to remove from the list.</param>
- public void RemoveEntry(int index)
- {
- if (index >= 0 && index < entries.Count)
- {
- entries.RemoveAt(index);
- contentsDirty = true;
- }
- }
- /// <summary>
- /// Removes all entries from the list.
- /// </summary>
- public void Clear()
- {
- entries.Clear();
- contentsDirty = true;
- }
- /// <summary>
- /// Finds an index of the specified entry in the list.
- /// </summary>
- /// <param name="data">Data of the entry to search for.</param>
- /// <returns>Index of the entry if found, -1 otherwise.</returns>
- public int FindEntry(TData data)
- {
- return entries.FindIndex(x => x.Equals(data));
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="index">Sequential index at which to insert the entry. </param>
- /// <param name="data">Data of the entry to insert.</param>
- public void InsertEntry(int index, TData data)
- {
- if (index >= 0 && index <= entries.Count)
- entries.Insert(index, data);
- else
- entries.Add(data);
- contentsDirty = true;
- }
- /// <summary>
- /// Changes the size of the list view.
- /// </summary>
- /// <param name="width">Width in pixels.</param>
- /// <param name="height">Height in pixels.</param>
- 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;
- }
- }
- /// <summary>
- /// Updates the visuals of the list view. Should be called once per frame.
- /// </summary>
- public abstract void Update();
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <typeparam name="TEntry">Type used for creating and updating the GUI elements of the visible entries.</typeparam>
- /// <typeparam name="TData">Type used for storing the data for all list entries.</typeparam>
- public class GUIListView<TEntry, TData> : GUIListViewBase<TData>
- where TEntry : GUIListViewEntry<TData>, new()
- where TData : GUIListViewData
- {
- private List<TEntry> visibleEntries = new List<TEntry>();
- /// <summary>
- /// Creates a new empty list view.
- /// </summary>
- /// <param name="width">Width of the list view, in pixels.</param>
- /// <param name="height">Height of the list view, in pixels.</param>
- /// <param name="entryHeight">Height of a single element in the list, in pixels.</param>
- /// <param name="layout">GUI layout into which the list view will be placed into.</param>
- public GUIListView(int width, int height, int entryHeight, GUILayout layout)
- :base(width, height, entryHeight, layout)
- { }
- /// <inheritdoc/>
- 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;
- }
- int totalElementHeight = entries.Count * entryHeight;
- if (scrollPct != scrollArea.VerticalScroll)
- {
- scrollPct = scrollArea.VerticalScroll;
- contentsDirty = true;
- if (scrollToLatest)
- {
- if (scrollPct < 1.0f)
- scrollToLatest = false;
- }
- else
- {
- if (totalElementHeight <= height || scrollPct >= 1.0f)
- scrollToLatest = true;
- }
- }
- if (contentsDirty)
- {
- int maxScrollOffset = MathEx.Max(0, totalElementHeight - height - 1);
- int startPos = MathEx.FloorToInt(scrollPct * 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]);
- visibleEntries[i].panel.SetPosition(0, i * entryHeight);
- }
- int bottomPosition = MathEx.Min(totalElementHeight, (startIndex + visibleEntries.Count) * entryHeight);
- bottomPadding.SetHeight(totalElementHeight - bottomPosition);
- if (scrollToLatest)
- {
- if (totalElementHeight <= height)
- scrollArea.VerticalScroll = 0.0f;
- else
- scrollArea.VerticalScroll = 1.0f;
- }
- contentsDirty = false;
- }
- }
- }
- /// <summary>
- /// Base class that contains data for individual entries used in <see cref="GUIListView{TEntry,TData}"/>.
- /// </summary>
- public class GUIListViewData
- {
-
- }
- /// <summary>
- /// Base class that displays GUI elements for visible entries used in <see cref="GUIListView{TEntry,TData}"/>.
- /// </summary>
- /// <typeparam name="TData">Type of object that contains data used for initializing the GUI elements.</typeparam>
- public abstract class GUIListViewEntry<TData>
- where TData : GUIListViewData
- {
- private GUIListViewBase<TData> parent;
- internal GUIPanel panel;
- internal GUILayoutY layout;
- protected GUILayout Layout { get { return layout; } }
- /// <summary>
- /// Initializes the GUI elements for the entry.
- /// </summary>
- /// <param name="parent">Scroll area into whose layout to insert the GUI elements.</param>
- internal void Initialize(GUIListViewBase<TData> 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();
- }
- /// <summary>
- /// Destoys the GUI elements for the entry.
- /// </summary>
- internal void Destroy()
- {
- panel.Destroy();
- }
- /// <summary>
- /// Causes all visible entries in the parent list to be updated.
- /// </summary>
- protected void RefreshEntries()
- {
- parent.contentsDirty = true;
- }
- /// <summary>
- /// Allows child classes to create GUI elements required by their entry specialization.
- /// </summary>
- public abstract void BuildGUI();
- /// <summary>
- /// Allows child classes to update GUI element(s) with new contents.
- /// </summary>
- /// <param name="index">Sequential index of the entry in the list.</param>
- /// <param name="data">Data of the entry to display.</param>
- public abstract void UpdateContents(int index, TData data);
- }
- }
|