Procházet zdrojové kódy

Fixing list view:
- GUIScrollArea will now properly update scroll position when set externally
- Fixed log message parsing
- Parsing log messages now also extracts method name
- Modified how console window selection works to to deal with more complex GUI entries and support re-usable nature of a list view
- Added documentation
- List view auto-scroll to latest when at scroll bottom
- Fixed an issue with an extra element when fully scrolled that was causing the list view to be constantly rebuilt

BearishSun před 10 roky
rodič
revize
bc4501a8f2

+ 5 - 5
BansheeEngine/Include/BsGUIElement.h

@@ -55,6 +55,11 @@ namespace BansheeEngine
 		 */
 		void setStyle(const String& styleName);
 
+		/**
+		 * @brief	Returns the name of the style used by this element.
+		 */
+		const String& getStyleName() const { return mStyleName; }
+
 		/**
 		 * @brief	Assigns a new context menu that will be opened when the element is right clicked.
 		 *			Null is allowed in case no context menu is wanted.
@@ -362,11 +367,6 @@ namespace BansheeEngine
 		 */
 		const String& getSubStyleName(const String& subStyleTypeName) const;
 
-		/**
-		 * @brief	Returns the name of the style used by this element.
-		 */
-		const String& getStyleName() const { return mStyleName; }
-
 		/**
 		 * @brief	Method that gets triggered whenever element style changes.
 		 */

+ 2 - 0
BansheeEngine/Include/BsGUIScrollArea.h

@@ -239,6 +239,8 @@ namespace BansheeEngine
 
 		float mVertOffset;
 		float mHorzOffset;
+		bool mRecalculateVertOffset;
+		bool mRecalculateHorzOffset;
 
 		Vector2I mVisibleSize;
 		Vector2I mContentSize;

+ 14 - 14
BansheeEngine/Source/BsGUIDimensions.cpp

@@ -93,9 +93,9 @@ namespace BansheeEngine
 
 		if (fixedHeight())
 		{
-			sizeRange.optimal.y = minHeight;
-			sizeRange.min.y = minHeight;
-			sizeRange.max.y = minHeight;
+			sizeRange.optimal.y = std::max(0, (INT32)minHeight);
+			sizeRange.min.y = sizeRange.optimal.y;
+			sizeRange.max.y = sizeRange.optimal.y;
 		}
 		else
 		{
@@ -103,22 +103,22 @@ namespace BansheeEngine
 
 			if (minHeight > 0)
 			{
-				sizeRange.optimal.y = std::max((INT32)minHeight, sizeRange.optimal.y);
-				sizeRange.min.y = minHeight;
+				sizeRange.optimal.y = std::max(std::max(0, (INT32)minHeight), sizeRange.optimal.y);
+				sizeRange.min.y = std::max(0, (INT32)minHeight);
 			}
 
 			if (maxHeight > 0)
 			{
-				sizeRange.optimal.y = std::min((INT32)maxHeight, sizeRange.optimal.y);
-				sizeRange.max.y = maxHeight;
+				sizeRange.optimal.y = std::min(std::max(0, (INT32)maxHeight), sizeRange.optimal.y);
+				sizeRange.max.y = std::max(0, (INT32)maxHeight);
 			}
 		}
 
 		if (fixedWidth())
 		{
-			sizeRange.optimal.x = minWidth;
-			sizeRange.min.x = minWidth;
-			sizeRange.max.x = minWidth;
+			sizeRange.optimal.x = std::max(0, (INT32)minWidth);
+			sizeRange.min.x = sizeRange.optimal.x;
+			sizeRange.max.x = sizeRange.optimal.x;
 		}
 		else
 		{
@@ -126,14 +126,14 @@ namespace BansheeEngine
 
 			if (minWidth > 0)
 			{
-				sizeRange.optimal.x = std::max((INT32)minWidth, sizeRange.optimal.x);
-				sizeRange.min.x = minWidth;
+				sizeRange.optimal.x = std::max(std::max(0, (INT32)minWidth), sizeRange.optimal.x);
+				sizeRange.min.x = std::max(0, (INT32)minWidth);
 			}
 
 			if (maxWidth > 0)
 			{
-				sizeRange.optimal.x = std::min((INT32)maxWidth, sizeRange.optimal.x);
-				sizeRange.max.x = maxWidth;
+				sizeRange.optimal.x = std::min(std::max(0, (INT32)maxWidth), sizeRange.optimal.x);
+				sizeRange.max.x = std::max(0, (INT32)maxWidth);
 			}
 		}
 

+ 38 - 10
BansheeEngine/Source/BsGUIScrollArea.cpp

@@ -24,7 +24,8 @@ namespace BansheeEngine
 	GUIScrollArea::GUIScrollArea(ScrollBarType vertBarType, ScrollBarType horzBarType, 
 		const String& scrollBarStyle, const String& scrollAreaStyle, const GUIDimensions& dimensions)
 		:GUIElementContainer(dimensions), mVertScroll(nullptr), mHorzScroll(nullptr), mVertOffset(0), mHorzOffset(0),
-		mVertBarType(vertBarType), mHorzBarType(horzBarType), mScrollBarStyle(scrollBarStyle)
+		mVertBarType(vertBarType), mHorzBarType(horzBarType), mScrollBarStyle(scrollBarStyle), mRecalculateVertOffset(false),
+		mRecalculateHorzOffset(false)
 	{
 		mContentLayout = GUILayoutY::create();
 		_registerChildElement(mContentLayout);
@@ -207,7 +208,7 @@ namespace BansheeEngine
 			}
 		}
 
-		elementAreas[layoutIdx] = Rect2I(layoutArea.x - Math::floorToInt(mHorzOffset), layoutArea.y - Math::floorToInt(mVertOffset), layoutWidth, layoutHeight);
+		elementAreas[layoutIdx] = Rect2I(layoutArea.x, layoutArea.y, layoutWidth, layoutHeight);
 
 		// Calculate vertical scrollbar bounds
 		if (hasVertScrollbar)
@@ -237,7 +238,7 @@ namespace BansheeEngine
 		else
 		{
 			elementAreas[horzScrollIdx] = Rect2I(layoutArea.x, layoutArea.y + layoutHeight, 0, 0);
-		}
+		}		
 	}
 
 	void GUIScrollArea::_updateLayoutInternal(const GUILayoutData& data)
@@ -271,9 +272,30 @@ namespace BansheeEngine
 		Rect2I& horzScrollBounds = elementAreas[horzScrollIdx];
 		Rect2I& vertScrollBounds = elementAreas[vertScrollIdx];
 
+		// Recalculate offsets in case scroll percent got updated externally (this needs to be delayed to this point because
+		// at the time of the update content and visible sizes might be out of date).
+		if (mRecalculateVertOffset)
+		{
+			UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(vertScrollBounds.height));
+			mVertOffset = scrollableHeight * Math::clamp01(mVertScroll->getScrollPos());
+
+			mRecalculateVertOffset = false;
+		}
+
+		if (mRecalculateHorzOffset)
+		{
+			UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(horzScrollBounds.width));
+			mHorzOffset = scrollableWidth * Math::clamp01(mHorzScroll->getScrollPos());
+
+			mRecalculateHorzOffset = false;
+		}
+
 		// Layout
 		if (mContentLayout->_isActive())
 		{
+			layoutBounds.x -= Math::floorToInt(mHorzOffset);
+			layoutBounds.y -= Math::floorToInt(mVertOffset);
+
 			Rect2I layoutClipRect = data.clipRect;
 			layoutClipRect.width = (UINT32)mVisibleSize.x;
 			layoutClipRect.height = (UINT32)mVisibleSize.y;
@@ -305,7 +327,7 @@ namespace BansheeEngine
 			float newScrollPct = 0.0f;
 
 			if (scrollableHeight > 0)
-				newScrollPct = mVertOffset / scrollableHeight;
+				newScrollPct = mVertOffset / scrollableHeight;	
 
 			mVertScroll->_setHandleSize(newHandleSize);
 			mVertScroll->_setScrollPos(newScrollPct);
@@ -342,26 +364,32 @@ namespace BansheeEngine
 
 	void GUIScrollArea::vertScrollUpdate(float scrollPos)
 	{
-		scrollToVertical(scrollPos);
+		UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
+		mVertOffset = scrollableHeight * Math::clamp01(scrollPos);
+
+		_markLayoutAsDirty();
 	}
 
 	void GUIScrollArea::horzScrollUpdate(float scrollPos)
 	{
-		scrollToHorizontal(scrollPos);
+		UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(mVisibleSize.x));
+		mHorzOffset = scrollableWidth * Math::clamp01(scrollPos);
+
+		_markLayoutAsDirty();
 	}
 
 	void GUIScrollArea::scrollToVertical(float pct)
 	{
-		UINT32 scrollableHeight = (UINT32)std::max(0, INT32(mContentSize.y) - INT32(mVisibleSize.y));
-		mVertOffset = scrollableHeight * Math::clamp01(pct);
+		mVertScroll->_setScrollPos(pct);
+		mRecalculateVertOffset = true;
 
 		_markLayoutAsDirty();
 	}
 
 	void GUIScrollArea::scrollToHorizontal(float pct)
 	{
-		UINT32 scrollableWidth = (UINT32)std::max(0, INT32(mContentSize.x) - INT32(mVisibleSize.x));
-		mHorzOffset = scrollableWidth * Math::clamp01(pct);
+		mHorzScroll->_setScrollPos(pct);
+		mRecalculateHorzOffset = true;
 
 		_markLayoutAsDirty();
 	}

+ 94 - 7
MBansheeEditor/ConsoleWindow.cs

@@ -19,7 +19,9 @@ namespace BansheeEditor
             Info = 0x01, Warning = 0x02, Error = 0x04, All = Info | Warning | Error
         }
 
-        private const int ENTRY_HEIGHT = 60;
+        private const int ENTRY_HEIGHT = 33;
+        private static int sSelectedElementIdx = -1;
+
         private GUIListView<ConsoleGUIEntry, ConsoleEntryData> listView;
         private List<ConsoleEntryData> entries = new List<ConsoleEntryData>();
         private EntryFilter filter = EntryFilter.All;
@@ -86,15 +88,16 @@ namespace BansheeEditor
             newEntry.type = type;
 
             int firstMatchIdx = -1;
-            Regex regex = new Regex(@"\tat .* in (.*), line (\d*), column .*, namespace .*");
+            Regex regex = new Regex(@"\tat (.*) in (.*), line (\d*), column .*, namespace .*");
             var matches = regex.Matches(message);
 
             newEntry.callstack = new ConsoleEntryData.CallStackEntry[matches.Count];
             for(int i = 0; i < matches.Count; i++)
             {
                 ConsoleEntryData.CallStackEntry callstackEntry = new ConsoleEntryData.CallStackEntry();
-                callstackEntry.file = matches[i].Groups[0].Value;
-                int.TryParse(matches[i].Groups[1].Value, out callstackEntry.line);
+                callstackEntry.method = matches[i].Groups[1].Value;
+                callstackEntry.file = matches[i].Groups[2].Value;
+                int.TryParse(matches[i].Groups[3].Value, out callstackEntry.line);
 
                 newEntry.callstack[i] = callstackEntry;
 
@@ -172,6 +175,7 @@ namespace BansheeEditor
             /// </summary>
             public class CallStackEntry
             {
+                public string method;
                 public string file;
                 public int line;
             }
@@ -186,19 +190,102 @@ namespace BansheeEditor
         /// </summary>
         private class ConsoleGUIEntry : GUIListViewEntry<ConsoleEntryData>
         {
+            // TODO - Create two separate labels for text and first callstack entry
+            // TODO - Add invisible button for overlay, toggle background selection when clicked
+            // TODO - Don't use toggle group, instead manually track which element is selected and update
+            //        selection state in UpdateContents()
+            // TODO - Remove ListView GUI states
+
+            private const int CALLER_LABEL_HEIGHT = 11;
+            private const int MESSAGE_HEIGHT = ENTRY_HEIGHT - CALLER_LABEL_HEIGHT;
+            private static readonly Color BG_COLOR = Color.DarkGray;
+            private static readonly Color SELECTION_COLOR = Color.DarkCyan;
+
+            private GUIPanel overlay;
+            private GUIPanel main;
+            private GUIPanel underlay;
+
             private GUILabel messageLabel;
+            private GUILabel functionLabel;
+            private GUITexture background;
+
+            private int entryIdx;
 
             /// <inheritdoc/>
             public override void BuildGUI()
             {
-                messageLabel = new GUILabel(new LocEdString(""), EditorStyles.MultiLineLabel, GUIOption.FixedHeight(ENTRY_HEIGHT));
-                Layout.AddElement(messageLabel);
+                main = Layout.AddPanel(0, 1, 1, GUIOption.FixedHeight(ENTRY_HEIGHT));
+                overlay = main.AddPanel(-1, 0, 0, GUIOption.FixedHeight(ENTRY_HEIGHT));
+                underlay = main.AddPanel(1, 0, 0, GUIOption.FixedHeight(ENTRY_HEIGHT));
+
+                GUILayoutY mainLayout = main.AddLayoutY();
+                GUILayoutY overlayLayout = overlay.AddLayoutY();
+                GUILayoutY underlayLayout = underlay.AddLayoutY();
+
+                messageLabel = new GUILabel(new LocEdString(""), EditorStyles.MultiLineLabel, GUIOption.FixedHeight(MESSAGE_HEIGHT));
+                functionLabel = new GUILabel(new LocEdString(""), GUIOption.FixedHeight(CALLER_LABEL_HEIGHT));
+
+                mainLayout.AddElement(messageLabel);
+                mainLayout.AddElement(functionLabel);
+
+                background = new GUITexture(Builtin.WhiteTexture, GUIOption.FixedHeight(ENTRY_HEIGHT));
+                underlayLayout.AddElement(background);
+
+                GUIButton button = new GUIButton(new LocEdString(""), EditorStyles.Blank, GUIOption.FixedHeight(ENTRY_HEIGHT));
+                overlayLayout.AddElement(button);
+
+                button.OnClick += OnClicked;
+                button.OnDoubleClick += OnDoubleClicked;
             }
 
             /// <inheritdoc/>
-            public override void UpdateContents(ConsoleEntryData data)
+            public override void UpdateContents(int index, ConsoleEntryData data)
             {
+                if (index != sSelectedElementIdx)
+                {
+                    if (index%2 != 0)
+                    {
+                        background.Visible = true;
+                        background.SetTint(BG_COLOR);
+                    }
+                    else
+                    {
+                        background.Visible = false;
+                    }
+                }
+                else
+                {
+                    background.Visible = true;
+                    background.SetTint(SELECTION_COLOR);
+                }
+
                 messageLabel.SetContent(new LocEdString(data.message));
+
+                string method = "";
+                if (data.callstack != null && data.callstack.Length > 0)
+                    method = data.callstack[0].method + " at " + data.callstack[0].file + ":" + data.callstack[0].line;
+
+                functionLabel.SetContent(new LocEdString(method));
+
+                entryIdx = index;
+            }
+
+            /// <summary>
+            /// Triggered when the entry is selected.
+            /// </summary>
+            private void OnClicked()
+            {
+                sSelectedElementIdx = entryIdx;
+
+                // TODO - Refresh all entries (especially previously selected one and this one to update their graphic)
+            }
+
+            /// <summary>
+            /// Triggered when the entry is double-clicked.
+            /// </summary>
+            private void OnDoubleClicked()
+            {
+                // TODO - Open code editor
             }
         }
     }

+ 2 - 2
MBansheeEngine/Color.cs

@@ -23,9 +23,9 @@ namespace BansheeEngine
         public static Color White { get { return new Color(1.0f, 1.0f, 1.0f, 1.0f); } }
         public static Color Black { get { return new Color(0.0f, 0.0f, 0.0f, 1.0f); } }
         public static Color DarkCyan { get { return new Color(0.0f, (114.0f / 255.0f), (188.0f / 255.0f), 1.0f); } }
-
+        public static Color DarkGray { get { return new Color(63.0f / 255.0f, 63.0f / 255.0f, 63.0f / 255.0f, 1.0f); } }
         public static Color BansheeOrange { get { return new Color(1.0f, (168.0f/255.0f), 0.0f, 1.0f); } }
-        
+
         /// <summary>
         /// Accesses color components by an index.
         /// </summary>

+ 15 - 0
MBansheeEngine/GUI/GUIElement.cs

@@ -28,6 +28,15 @@ namespace BansheeEngine
             get { return Internal_GetParent(mCachedPtr); }
         }
 
+        /// <summary>
+        /// Name of the style that determines the appearance of this GUI element.
+        /// </summary>
+        public string Style
+        {
+            get { return Internal_GetStyle(mCachedPtr); }
+            set { Internal_SetStyle(mCachedPtr, value); }
+        }
+
         /// <summary>
         /// Gets or sets non-clipped bounds of the GUI element. Relative to a parent GUI panel.
         /// </summary>
@@ -231,6 +240,12 @@ namespace BansheeEngine
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetContextMenu(IntPtr nativeInstance, IntPtr contextMenu);
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern string Internal_GetStyle(IntPtr nativeInstance);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetStyle(IntPtr nativeInstance, string style);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_Destroy(IntPtr nativeInstance);
     }

+ 104 - 9
MBansheeEngine/GUI/GUIListView.cs

@@ -2,7 +2,13 @@
 
 namespace BansheeEngine
 {
-    // TODO - Doc
+    /// <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> 
         where TEntry : GUIListViewEntry<TData>, new()
         where TData : GUIListViewData
@@ -20,19 +26,33 @@ namespace BansheeEngine
         private int height;
         private int entryHeight;
         private float scrollPct = 0.0f;
+        private bool scrollToLatest = true;
         private 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>
+        /// 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)
         {
             scrollArea = new GUIScrollArea(GUIOption.FixedWidth(width), GUIOption.FixedHeight(height));
@@ -49,12 +69,20 @@ namespace BansheeEngine
             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)
@@ -64,17 +92,30 @@ namespace BansheeEngine
             }
         }
 
+        /// <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 == 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)
@@ -85,6 +126,11 @@ namespace BansheeEngine
             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)
@@ -99,6 +145,9 @@ namespace BansheeEngine
             }
         }
 
+        /// <summary>
+        /// Updates the visuals of the list view. Should be called once per frame.
+        /// </summary>
         public void Update()
         {
             int numVisibleEntries = MathEx.CeilToInt(height / (float)entryHeight) + 1;
@@ -124,41 +173,71 @@ namespace BansheeEngine
                 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 totalElementHeight = entries.Count*entryHeight;
-                int scrollableHeight = MathEx.Max(0, totalElementHeight - height - 1);
+                int maxScrollOffset = MathEx.Max(0, totalElementHeight - height - 1);
 
-                int startPos = MathEx.FloorToInt(scrollPct*scrollableHeight);
-                int startIndex = startPos/entryHeight;
+                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(entries[startIndex + i]);
+                    visibleEntries[i].UpdateContents(startIndex + i, entries[startIndex + i]);
                     visibleEntries[i].panel.SetPosition(0, i * entryHeight);
                 }
 
-                int bottomPosition = (startIndex + visibleEntries.Count)*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
     {
@@ -167,6 +246,10 @@ namespace BansheeEngine
 
         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(GUIScrollArea parent)
         {
             int numElements = parent.Layout.ChildCount;
@@ -178,12 +261,24 @@ namespace BansheeEngine
             BuildGUI();
         }
 
+        /// <summary>
+        /// Destoys the GUI elements for the entry.
+        /// </summary>
         internal void Destroy()
         {
             panel.Destroy();
         }
 
+        /// <summary>
+        /// Allows child classes to create GUI elements required by their entry specialization.
+        /// </summary>
         public abstract void BuildGUI();
-        public abstract void UpdateContents(TData data);
+
+        /// <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);
     }
 }

+ 2 - 0
SBansheeEngine/Include/BsScriptGUIElement.h

@@ -148,6 +148,8 @@ namespace BansheeEngine
 		static void internal_SetFlexibleHeight(ScriptGUIElementBaseTBase* nativeInstance, UINT32 minHeight, UINT32 maxHeight);
 		static void internal_SetContextMenu(ScriptGUIElementBaseTBase* nativeInstance, ScriptContextMenu* contextMenu);
 		static void internal_ResetDimensions(ScriptGUIElementBaseTBase* nativeInstance);
+		static MonoString* internal_GetStyle(ScriptGUIElementBaseTBase* nativeInstance);
+		static void internal_SetStyle(ScriptGUIElementBaseTBase* nativeInstance, MonoString* style);
 
 		typedef void(__stdcall *OnFocusChangedThunkDef) (MonoObject*, MonoException**);
 

+ 30 - 0
SBansheeEngine/Source/BsScriptGUIElement.cpp

@@ -94,6 +94,8 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_SetFlexibleHeight", &ScriptGUIElement::internal_SetFlexibleHeight);
 		metaData.scriptClass->addInternalCall("Internal_ResetDimensions", &ScriptGUIElement::internal_ResetDimensions);
 		metaData.scriptClass->addInternalCall("Internal_SetContextMenu", &ScriptGUIElement::internal_SetContextMenu);
+		metaData.scriptClass->addInternalCall("Internal_GetStyle", &ScriptGUIElement::internal_GetStyle);
+		metaData.scriptClass->addInternalCall("Internal_SetStyle", &ScriptGUIElement::internal_SetStyle);
 
 		onFocusGainedThunk = (OnFocusChangedThunkDef)metaData.scriptClass->getMethod("Internal_OnFocusGained", 0)->getThunk();
 		onFocusLostThunk = (OnFocusChangedThunkDef)metaData.scriptClass->getMethod("Internal_OnFocusLost", 0)->getThunk();
@@ -243,4 +245,32 @@ namespace BansheeEngine
 			guiElem->setContextMenu(nativeContextMenu);
 		}
 	}
+
+	MonoString* ScriptGUIElement::internal_GetStyle(ScriptGUIElementBaseTBase* nativeInstance)
+	{
+		if (!nativeInstance->isDestroyed())
+		{
+			GUIElementBase* guiElemBase = nativeInstance->getGUIElement();
+			if (guiElemBase->_getType() == GUIElementBase::Type::Element)
+			{
+				GUIElement* guiElem = static_cast<GUIElement*>(guiElemBase);
+				return MonoUtil::stringToMono(MonoManager::instance().getDomain(), guiElem->getStyleName());
+			}
+		}
+
+		return MonoUtil::stringToMono(MonoManager::instance().getDomain(), StringUtil::BLANK);
+	}
+
+	void ScriptGUIElement::internal_SetStyle(ScriptGUIElementBaseTBase* nativeInstance, MonoString* style)
+	{
+		if (!nativeInstance->isDestroyed())
+		{
+			GUIElementBase* guiElemBase = nativeInstance->getGUIElement();
+			if (guiElemBase->_getType() == GUIElementBase::Type::Element)
+			{
+				GUIElement* guiElem = static_cast<GUIElement*>(guiElemBase);
+				guiElem->setStyle(MonoUtil::monoToString(style));
+			}
+		}
+	}
 }

+ 1 - 0
TODO.txt

@@ -55,6 +55,7 @@ Optional:
  - MenuBar - will likely need a way to mark elements as disabled when not appropriate (e.g. no "frame selected unless scene is focused")
    - Likely use a user-provided callback to trigger when populating the menus (I already added a callback to MenuItem, just need to implement it)
  - Need to list all script components in the Components menu
+ - Cursors should be replaced with better ones, or at least hot-spots fixed
 
 Seriously optional:
  - Drag to select in scene view