Browse Source

Refactored GUI list so it doesn't need to be fully rebuild with element is added or removed

BearishSun 10 years ago
parent
commit
e277f5bfe7

+ 5 - 4
MBansheeEditor/GUI/GUIDictionaryField.cs

@@ -291,7 +291,7 @@ namespace BansheeEditor
         /// <summary>
         /// Checks is the dictionary instance not assigned.
         /// </summary>
-        /// <returns>True if there is not dictionary instance.</returns>
+        /// <returns>True if there is not a dictionary instance.</returns>
         protected abstract bool IsNull();
 
         /// <summary>
@@ -352,7 +352,7 @@ namespace BansheeEditor
         /// <summary>
         /// Creates a new dictionary row GUI.
         /// </summary>
-        /// <returns>Object containing a dictionary row GUI.</returns>
+        /// <returns>Object containing the dictionary row GUI.</returns>
         protected abstract GUIDictionaryFieldRow CreateRow();
 
         /// <summary>
@@ -835,7 +835,7 @@ namespace BansheeEditor
         private List<Key> orderedKeys = new List<Key>();
 
         /// <summary>
-        /// Constructs a new GUI dictionary.
+        /// Constructs a new dictionary GUI field.
         /// </summary>
         /// <param name="title">Label to display on the dictionary GUI title.</param>
         /// <param name="dictionary">Object containing the data. Can be null.</param>
@@ -851,7 +851,7 @@ namespace BansheeEditor
         }
 
         /// <summary>
-        /// Builds the dictionary GUI elements. Must be called at least once in order for the contents to be populated.
+        /// Creates a dictionary GUI field containing GUI elements required for displaying the provided dictionary.
         /// </summary>
         /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual dictionary elements.</typeparam>
         /// <param name="title">Label to display on the list GUI title.</param>
@@ -860,6 +860,7 @@ namespace BansheeEditor
         /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
         ///                     nested containers whose backgrounds are overlaping. Also determines background style,
         ///                     depths divisible by two will use an alternate style.</param>
+        /// <returns>New instance of dictionary GUI field.</returns>
         public static GUIDictionaryField<Key, Value, RowType> Create(LocString title, Dictionary<Key, Value> dictionary, 
             GUILayout layout, int depth = 0)
 

+ 365 - 113
MBansheeEditor/GUI/GUIListField.cs

@@ -13,52 +13,76 @@ namespace BansheeEditor
         private const int IndentAmount = 5;
 
         protected List<GUIListFieldRow> rows = new List<GUIListFieldRow>();
+        protected GUILayoutY guiLayout;
         protected GUIIntField guiSizeField;
         protected GUILayoutX guiChildLayout;
         protected GUILayoutX guiTitleLayout;
         protected GUILayoutY guiContentLayout;
+
         protected bool isExpanded;
         protected int depth;
+        protected LocString title;
 
-        /// <summary>
-        /// Constructs a new GUI list.
-        /// </summary>
-        protected GUIListFieldBase()
-        { }
+        private State state;
 
         /// <summary>
-        /// Builds the list GUI elements. Must be called at least once in order for the contents to be populated.
+        /// Constructs a new GUI list.
         /// </summary>
-        /// <typeparam name="T">Type of rows that are used to handle GUI for individual list elements.</typeparam>
         /// <param name="title">Label to display on the list GUI title.</param>
-        /// <param name="empty">Should the created field represent a null object.</param>
-        /// <param name="numRows">Number of rows to create GUI for. Only matters for a non-null list.</param>
-        /// <param name="layout">Layout to which to append the list GUI elements to.</param>
+        /// <param name="layout">Layout to which to append the array GUI elements to.</param>
         /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
         ///                     nested containers whose backgrounds are overlaping. Also determines background style,
         ///                     depths divisible by two will use an alternate style.</param>
-        protected void BuildGUI<T>(LocString title, bool empty, int numRows, GUILayout layout, 
-            int depth = 0) where T : GUIListFieldRow, new()
+        protected GUIListFieldBase(LocString title, GUILayout layout, int depth)
         {
-            Destroy();
-
+            this.title = title;
             this.depth = depth;
+            this.guiLayout = layout.AddLayoutY();
+        }
+
+        /// <summary>
+        /// Builds the list GUI elements. Must be called at least once in order for the contents to be populated.
+        /// </summary>
+        protected void BuildGUI()
+        {
+            UpdateHeaderGUI(true);
 
-            if (empty)
+            if (!IsNull())
             {
-                guiChildLayout = null;
-                guiContentLayout = null;
-                guiTitleLayout = layout.AddLayoutX();
+                // Hidden dependency: BuildGUI must be called after all elements are 
+                // in the dictionary so we do it in two steps
+                int numRows = GetNumRows();
+                for (int i = 0; i < numRows; i++)
+                {
+                    GUIListFieldRow newRow = CreateRow();
+                    rows.Add(newRow);
+                }
+
+                for (int i = 0; i < numRows; i++)
+                    rows[i].BuildGUI(this, guiContentLayout, i, depth + 1);
+            }
+        }
+
+        /// <summary>
+        /// Rebuilds the GUI list header if needed.
+        /// </summary>
+        /// <param name="forceRebuild">Forces the header to be rebuilt.</param>
+        protected void UpdateHeaderGUI(bool forceRebuild)
+        {
+            Action BuildEmptyGUI = () =>
+            {
+                guiTitleLayout = guiLayout.AddLayoutX();
 
                 guiTitleLayout.AddElement(new GUILabel(title));
                 guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
 
                 GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create));
                 GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
-                createBtn.OnClick += OnCreateButtonClicked;
+                createBtn.OnClick += CreateList;
                 guiTitleLayout.AddElement(createBtn);
-            }
-            else
+            };
+
+            Action BuildFilledGUI = () =>
             {
                 GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
                 guiFoldout.Value = isExpanded;
@@ -74,54 +98,104 @@ namespace BansheeEditor
                 GUIButton guiClearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(30));
                 guiClearBtn.OnClick += OnClearButtonClicked;
 
-                guiTitleLayout = layout.AddLayoutX();
+                guiTitleLayout = guiLayout.AddLayoutX();
                 guiTitleLayout.AddElement(guiFoldout);
                 guiTitleLayout.AddElement(guiSizeField);
                 guiTitleLayout.AddElement(guiResizeBtn);
                 guiTitleLayout.AddElement(guiClearBtn);
 
-                guiSizeField.Value = numRows;
+                guiSizeField.Value = GetNumRows();
+
+                guiChildLayout = guiLayout.AddLayoutX();
+                guiChildLayout.AddSpace(IndentAmount);
+                guiChildLayout.Active = isExpanded;
+
+                GUIPanel guiContentPanel = guiChildLayout.AddPanel();
+                GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX();
+                guiIndentLayoutX.AddSpace(IndentAmount);
+                GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY();
+                guiIndentLayoutY.AddSpace(IndentAmount);
+                guiContentLayout = guiIndentLayoutY.AddLayoutY();
+                guiIndentLayoutY.AddSpace(IndentAmount);
+                guiIndentLayoutX.AddSpace(IndentAmount);
+                guiChildLayout.AddSpace(IndentAmount);
+
+                short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1);
+                string bgPanelStyle = depth % 2 == 0
+                    ? EditorStyles.InspectorContentBgAlternate
+                    : EditorStyles.InspectorContentBg;
+
+                GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth);
+                GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
+                backgroundPanel.AddElement(inspectorContentBg);
+            };
+
+            if (forceRebuild)
+            {
+                if (state != State.None)
+                    guiTitleLayout.Destroy();
+
+                if (state == State.Filled)
+                    guiChildLayout.Destroy();
+
+                state = State.None;
+            }
 
-                if (numRows > 0)
+            if (state == State.None)
+            {
+                if (!IsNull())
+                {
+                    BuildFilledGUI();
+                    state = State.Filled;
+                }
+                else
+                {
+                    BuildEmptyGUI();
+
+                    state = State.Empty;
+                }
+            }
+            else if (state == State.Empty)
+            {
+                if (!IsNull())
+                {
+                    guiTitleLayout.Destroy();
+                    BuildFilledGUI();
+                    state = State.Filled;
+                }
+            }
+            else if (state == State.Filled)
+            {
+                if (IsNull())
                 {
-                    guiChildLayout = layout.AddLayoutX();
-                    guiChildLayout.AddSpace(IndentAmount);
-                    guiChildLayout.Active = isExpanded;
-
-                    GUIPanel guiContentPanel = guiChildLayout.AddPanel();
-                    GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX();
-                    guiIndentLayoutX.AddSpace(IndentAmount);
-                    GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY();
-                    guiIndentLayoutY.AddSpace(IndentAmount);
-                    guiContentLayout = guiIndentLayoutY.AddLayoutY();
-                    guiIndentLayoutY.AddSpace(IndentAmount);
-                    guiIndentLayoutX.AddSpace(IndentAmount);
-                    guiChildLayout.AddSpace(IndentAmount);
-
-                    short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1);
-                    string bgPanelStyle = depth % 2 == 0
-                        ? EditorStyles.InspectorContentBgAlternate
-                        : EditorStyles.InspectorContentBg;
-
-                    GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth);
-                    GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
-                    backgroundPanel.AddElement(inspectorContentBg);
-
-                    for (int i = rows.Count; i < numRows; i++)
-                    {
-                        GUIListFieldRow newRow = new T();
-                        rows.Add(newRow);
-                    }
-
-                    while (rows.Count > numRows)
-                        rows.RemoveAt(rows.Count - 1);
-
-                    for (int i = 0; i < numRows; i++)
-                        rows[i].BuildGUI(this, guiContentLayout, i, depth);
+                    guiTitleLayout.Destroy();
+                    guiChildLayout.Destroy();
+                    BuildEmptyGUI();
+
+                    state = State.Empty;
                 }
             }
         }
 
+        /// <summary>
+        /// Rebuilds GUI for all existing list rows.
+        /// </summary>
+        private void BuildRows()
+        {
+            foreach (var row in rows)
+                row.Destroy();
+
+            if (!IsNull())
+            {
+                for (int i = 0; i < rows.Count; i++)
+                    rows[i].BuildGUI(this, guiContentLayout, i, depth + 1);
+            }
+            else
+            {
+                rows.Clear();
+            }
+        }
+
         /// <summary>
         /// Returns the layout that is used for positioning the elements in the title bar.
         /// </summary>
@@ -164,6 +238,24 @@ namespace BansheeEditor
                 rows[i].Destroy();
         }
 
+        /// <summary>
+        /// Creates a new list row GUI.
+        /// </summary>
+        /// <returns>Object containing the list row GUI.</returns>
+        protected abstract GUIListFieldRow CreateRow();
+
+        /// <summary>
+        /// Checks is the list instance not assigned.
+        /// </summary>
+        /// <returns>True if there is not a list instance.</returns>
+        protected abstract bool IsNull();
+
+        /// <summary>
+        /// Returns the number of rows in the list.
+        /// </summary>
+        /// <returns>Number of rows in the list.</returns>
+        protected abstract int GetNumRows();
+
         /// <summary>
         /// Gets a value of an element at the specified index in the list.
         /// </summary>
@@ -194,45 +286,142 @@ namespace BansheeEditor
         /// Triggered when the user clicks on the create button on the title bar. Creates a brand new list with zero
         /// elements in the place of the current list.
         /// </summary>
-        protected abstract void OnCreateButtonClicked();
+        protected void OnCreateButtonClicked()
+        {
+            CreateList();
+
+            UpdateHeaderGUI(false);
+        }
 
         /// <summary>
         /// Triggered when the user clicks on the resize button on the title bar. Changes the size of the list while
         /// preserving existing contents.
         /// </summary>
-        protected abstract void OnResizeButtonClicked();
+        protected void OnResizeButtonClicked()
+        {
+            ResizeList();
+
+            BuildRows();
+        }
 
         /// <summary>
         /// Triggered when the user clicks on the clear button on the title bar. Deletes the current list object.
         /// </summary>
-        protected abstract void OnClearButtonClicked();
+        protected void OnClearButtonClicked()
+        {
+            ClearList();
+
+            UpdateHeaderGUI(false);
+            BuildRows();
+        }
 
         /// <summary>
         /// Triggered when the user clicks on the delete button next to a list entry. Deletes an element in the list.
         /// </summary>
         /// <param name="index">Sequential index of the element in the list to remove.</param>
-        protected internal abstract void OnDeleteButtonClicked(int index);
+        protected internal void OnDeleteButtonClicked(int index)
+        {
+            DeleteElement(index);
+
+            if (rows.Count > 0)
+            {
+                rows[rows.Count - 1].Destroy();
+                rows.RemoveAt(rows.Count - 1);
+            }
+
+            guiSizeField.Value = GetNumRows();
+            BuildRows();
+        }
 
         /// <summary>
         /// Triggered when the user clicks on the clone button next to a list entry. Clones the element and adds the clone 
         /// to the back of the list. 
         /// </summary>
         /// <param name="index">Sequential index of the element in the list to clone.</param>
-        protected internal abstract void OnCloneButtonClicked(int index);
+        protected internal void OnCloneButtonClicked(int index)
+        {
+            CloneElement(index);
+
+            GUIListFieldRow row = CreateRow();
+            rows.Add(row);
+
+            guiSizeField.Value = GetNumRows();
+            BuildRows();
+        }
 
         /// <summary>
         /// Triggered when the user clicks on the move up button next to a list entry. Moves an element from the current
         /// list index to the one right before it, if not at zero.
         /// </summary>
         /// <param name="index">Sequential index of the element in the list to move.</param>
-        protected internal abstract void OnMoveUpButtonClicked(int index);
+        protected internal void OnMoveUpButtonClicked(int index)
+        {
+            MoveUpElement(index);
+
+            BuildRows();
+        }
 
         /// <summary>
         /// Triggered when the user clicks on the move down button next to a list entry. Moves an element from the current
         /// list index to the one right after it, if the element isn't already the last element.
         /// </summary>
         /// <param name="index">Sequential index of the element in the list to move.</param>
-        protected internal abstract void OnMoveDownButtonClicked(int index);
+        protected internal void OnMoveDownButtonClicked(int index)
+        {
+            MoveDownElement(index);
+
+            BuildRows();
+        }
+
+        /// <summary>
+        /// Creates a brand new list with zero elements in the place of the current list.
+        /// </summary>
+        protected abstract void CreateList();
+
+        /// <summary>
+        /// Changes the size of the list while preserving existing contents.
+        /// </summary>
+        protected abstract void ResizeList();
+
+        /// <summary>
+        /// Deletes the current list object.
+        /// </summary>
+        protected abstract void ClearList();
+
+        /// <summary>
+        /// Deletes an element in the list.
+        /// </summary>
+        /// <param name="index">Sequential index of the element in the list to remove.</param>
+        protected internal abstract void DeleteElement(int index);
+
+        /// <summary>
+        /// Clones the element and adds the clone to the back of the list. 
+        /// </summary>
+        /// <param name="index">Sequential index of the element in the list to clone.</param>
+        protected internal abstract void CloneElement(int index);
+
+        /// <summary>
+        /// Moves an element from the current list index to the one right before it, if not at zero.
+        /// </summary>
+        /// <param name="index">Sequential index of the element in the list to move.</param>
+        protected internal abstract void MoveUpElement(int index);
+
+        /// <summary>
+        /// Moves an element from the current list index to the one right after it, if the element isn't already the last 
+        /// element.
+        /// </summary>
+        /// <param name="index">Sequential index of the element in the list to move.</param>
+        protected internal abstract void MoveDownElement(int index);
+
+        /// <summary>
+        /// Possible states list GUI can be in.
+        /// </summary>
+        private enum State
+        {
+            None,
+            Empty,
+            Filled
+        }
     }
 
     /// <summary>
@@ -240,7 +429,8 @@ namespace BansheeEditor
     /// object user can provide a custom type that manages GUI for individual array elements.
     /// </summary>
     /// <typeparam name="ElementType">Type of elements stored in the array.</typeparam>
-    public class GUIArrayField<ElementType> : GUIListFieldBase
+    /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual array elements.</typeparam>
+    public class GUIArrayField<ElementType, RowType> : GUIListFieldBase where RowType : GUIListFieldRow, new() 
     {
         /// <summary>
         /// Triggered when the reference array has been changed. This does not include changes that only happen to its 
@@ -260,30 +450,59 @@ namespace BansheeEditor
         protected ElementType[] array;
 
         /// <summary>
-        /// Constructs a new empty GUI array.
+        /// Constructs a new GUI array field.
         /// </summary>
-        public GUIArrayField()
-        { }
+        /// <param name="title">Label to display on the array GUI title.</param>
+        /// <param name="array">Object containing the array data. Can be null.</param>
+        /// <param name="layout">Layout to which to append the array GUI elements to.</param>
+        /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
+        ///                     nested containers whose backgrounds are overlaping. Also determines background style,
+        ///                     depths divisible by two will use an alternate style.</param>
+        protected GUIArrayField(LocString title, ElementType[] array, GUILayout layout, int depth = 0)
+            :base(title, layout, depth)
+        {
+            this.array = array;
+        }
 
         /// <summary>
-        /// Builds the array GUI elements. Must be called at least once in order for the contents to be populated.
+        /// Creates a array GUI field containing GUI elements for displaying an array.
         /// </summary>
-        /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual array elements.</typeparam>
         /// <param name="title">Label to display on the array GUI title.</param>
         /// <param name="array">Object containing the array data. Can be null.</param>
         /// <param name="layout">Layout to which to append the array GUI elements to.</param>
         /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
         ///                     nested containers whose backgrounds are overlaping. Also determines background style,
         ///                     depths divisible by two will use an alternate style.</param>
-        public void BuildGUI<RowType>(LocString title, ElementType[] array, GUILayout layout, int depth = 0) 
-            where RowType : GUIListFieldRow, new() 
+        /// <returns>New instance of an array GUI field.</returns>
+        public static GUIArrayField<ElementType, RowType> Create(LocString title, ElementType[] array, GUILayout layout, 
+            int depth = 0)
         {
-            this.array = array;
+            GUIArrayField<ElementType, RowType> guiArray = new GUIArrayField<ElementType, RowType>(title, array, layout,
+                depth);
+
+            guiArray.BuildGUI();
+            return guiArray;
+        }
+
+        /// <inheritdoc/>
+        protected override GUIListFieldRow CreateRow()
+        {
+            return new RowType();
+        }
+
+        /// <inheritdoc/>
+        protected override bool IsNull()
+        {
+            return array == null;
+        }
 
+        /// <inheritdoc/>
+        protected override int GetNumRows()
+        {
             if (array != null)
-                base.BuildGUI<RowType>(title, false, array.Length, layout, depth);
-            else
-                base.BuildGUI<RowType>(title, true, 0, layout, depth);
+                return array.GetLength(0);
+
+            return 0;
         }
 
         /// <inheritdoc/>
@@ -302,7 +521,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected override void OnCreateButtonClicked()
+        protected override void CreateList()
         {
             array = new ElementType[0];
 
@@ -311,7 +530,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected override void OnResizeButtonClicked()
+        protected override void ResizeList()
         {
             int size = guiSizeField.Value;
 
@@ -329,7 +548,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected override void OnClearButtonClicked()
+        protected override void ClearList()
         {
             array = null;
 
@@ -338,7 +557,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected internal override void OnDeleteButtonClicked(int index)
+        protected internal override void DeleteElement(int index)
         {
             int size = MathEx.Max(0, array.GetLength(0) - 1);
             ElementType[] newArray = new ElementType[size];
@@ -360,7 +579,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected internal override void OnCloneButtonClicked(int index)
+        protected internal override void CloneElement(int index)
         {
             int size = array.GetLength(0) + 1;
             ElementType[] newArray = new ElementType[size];
@@ -389,7 +608,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected internal override void OnMoveUpButtonClicked(int index)
+        protected internal override void MoveUpElement(int index)
         {
             if ((index - 1) >= 0)
             {
@@ -404,7 +623,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected internal override void OnMoveDownButtonClicked(int index)
+        protected internal override void MoveDownElement(int index)
         {
             if ((index + 1) < array.GetLength(0))
             {
@@ -424,7 +643,8 @@ namespace BansheeEditor
     /// object user can provide a custom type that manages GUI for individual list elements.
     /// </summary>
     /// <typeparam name="ElementType">Type of elements stored in the list.</typeparam>
-    public class GUIListField<ElementType> : GUIListFieldBase
+    /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual list elements.</typeparam>
+    public class GUIListField<ElementType, RowType> : GUIListFieldBase where RowType : GUIListFieldRow, new()
     {
         /// <summary>
         /// Triggered when the reference list has been changed. This does not include changes that only happen to its 
@@ -444,30 +664,58 @@ namespace BansheeEditor
         protected List<ElementType> list;
 
         /// <summary>
-        /// Constructs a new empty GUI array.
+        /// Constructs a new GUI list field.
         /// </summary>
-        public GUIListField()
-        { }
+        /// <param name="title">Label to display on the list GUI title.</param>
+        /// <param name="list">Object containing the list data. Can be null.</param>
+        /// <param name="layout">Layout to which to append the list GUI elements to.</param>
+        /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
+        ///                     nested containers whose backgrounds are overlaping. Also determines background style,
+        ///                     depths divisible by two will use an alternate style.</param>
+        protected GUIListField(LocString title, List<ElementType> list, GUILayout layout, int depth = 0)
+            : base(title, layout, depth)
+        {
+            this.list = list;
+        }
 
         /// <summary>
-        /// Builds the list GUI elements. Must be called at least once in order for the contents to be populated.
+        /// Creates a list GUI field containing GUI elements for displaying a list.
         /// </summary>
-        /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual list elements.</typeparam>
         /// <param name="title">Label to display on the list GUI title.</param>
         /// <param name="list">Object containing the list data. Can be null.</param>
         /// <param name="layout">Layout to which to append the list GUI elements to.</param>
         /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
         ///                     nested containers whose backgrounds are overlaping. Also determines background style,
         ///                     depths divisible by two will use an alternate style.</param>
-        public void BuildGUI<RowType>(LocString title, List<ElementType> list, GUILayout layout, int depth = 0)
-            where RowType : GUIListFieldRow, new()
+        /// <returns>New instance of a list GUI field.</returns>
+        public static GUIListField<ElementType, RowType> Create(LocString title, List<ElementType> list, GUILayout layout, 
+            int depth = 0)
         {
-            this.list = list;
+            GUIListField<ElementType, RowType> guiList = new GUIListField<ElementType, RowType>(title, list, layout, depth);
+            guiList.BuildGUI();
+
+            return guiList;
+        }
+
+        /// <inheritdoc/>
+        protected override GUIListFieldRow CreateRow()
+        {
+            return new RowType();
+        }
 
+        /// <inheritdoc/>
+        protected override bool IsNull()
+        {
+            return list == null;
+        }
+
+        /// <inheritdoc/>
+        protected override int GetNumRows()
+        {
             if (list != null)
-                base.BuildGUI<RowType>(title, false, list.Count, layout, depth);
-            else
-                base.BuildGUI<RowType>(title, true, 0, layout, depth);
+                return list.Count;
+
+            return 0;
         }
 
         /// <inheritdoc/>
@@ -486,7 +734,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected override void OnCreateButtonClicked()
+        protected override void CreateList()
         {
             list = new List<ElementType>();
 
@@ -495,7 +743,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected override void OnResizeButtonClicked()
+        protected override void ResizeList()
         {
             int size = guiSizeField.Value;
             if(size == list.Count)
@@ -509,12 +757,12 @@ namespace BansheeEditor
                 list.AddRange(extraElements);
             }
 
-            if (OnChanged != null)
+            if (OnValueChanged != null)
                 OnValueChanged();
         }
 
         /// <inheritdoc/>
-        protected override void OnClearButtonClicked()
+        protected override void ClearList()
         {
             list = null;
 
@@ -523,16 +771,16 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected internal override void OnDeleteButtonClicked(int index)
+        protected internal override void DeleteElement(int index)
         {
             list.RemoveAt(index);
 
-            if (OnChanged != null)
-                OnChanged(list);
+            if (OnValueChanged != null)
+                OnValueChanged();
         }
 
         /// <inheritdoc/>
-        protected internal override void OnCloneButtonClicked(int index)
+        protected internal override void CloneElement(int index)
         {
             object clonedEntry = null;
             if (list[index] != null)
@@ -540,12 +788,12 @@ namespace BansheeEditor
 
             list.Add((ElementType)clonedEntry);
 
-            if (OnChanged != null)
-                OnChanged(list);
+            if (OnValueChanged != null)
+                OnValueChanged();
         }
 
         /// <inheritdoc/>
-        protected internal override void OnMoveUpButtonClicked(int index)
+        protected internal override void MoveUpElement(int index)
         {
             if ((index - 1) >= 0)
             {
@@ -560,7 +808,7 @@ namespace BansheeEditor
         }
 
         /// <inheritdoc/>
-        protected internal override void OnMoveDownButtonClicked(int index)
+        protected internal override void MoveDownElement(int index)
         {
             if ((index + 1) < list.Count)
             {
@@ -606,15 +854,15 @@ namespace BansheeEditor
         /// <param name="depth">Determines the depth at which the element is rendered.</param>
         internal void BuildGUI(GUIListFieldBase parent, GUILayout parentLayout, int seqIndex, int depth)
         {
+            if (rowLayout != null)
+                rowLayout.Destroy();
+
             this.parent = parent;
             this.seqIndex = seqIndex;
             this.depth = depth;
 
-            if (rowLayout == null)
-                rowLayout = parentLayout.AddLayoutX();
-
-            if (contentLayout == null)
-                contentLayout = rowLayout.AddLayoutY();
+            rowLayout = parentLayout.AddLayoutX();
+            contentLayout = rowLayout.AddLayoutY();
 
             GUILayoutX externalTitleLayout = CreateGUI(contentLayout);
             if (localTitleLayout || (titleLayout != null && titleLayout == externalTitleLayout))
@@ -702,6 +950,10 @@ namespace BansheeEditor
                 rowLayout.Destroy();
                 rowLayout = null;
             }
+
+            contentLayout = null;
+            titleLayout = null;
+            localTitleLayout = false;
         }
     }
 }

+ 43 - 24
MBansheeEditor/Inspector/InspectableArray.cs

@@ -11,7 +11,7 @@ namespace BansheeEditor
     {
         private object propertyValue; // TODO - This will unnecessarily hold references to the object
         private int numArrayElements;
-        private InspectableArrayGUI arrayGUIField = new InspectableArrayGUI();
+        private InspectableArrayGUI arrayGUIField;
 
         /// <summary>
         /// Creates a new inspectable array GUI for the specified property.
@@ -70,7 +70,7 @@ namespace BansheeEditor
         {
             GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
 
-            arrayGUIField.BuildGUI(title, property, arrayLayout, depth);
+            arrayGUIField = InspectableArrayGUI.Create(title, property, arrayLayout, depth);
         }
 
         /// <inheritdoc/>
@@ -97,14 +97,16 @@ namespace BansheeEditor
             private SerializableProperty property;
 
             /// <summary>
-            /// Constructs a new empty inspectable array GUI.
+            /// Constructs a new inspectable array GUI.
             /// </summary>
-            public InspectableArrayGUI()
-            { }
+            public InspectableArrayGUI(LocString title, SerializableProperty property, GUILayout layout, int depth)
+                : base(title, layout, depth)
+            {
+                this.property = property;
+            }
 
             /// <summary>
-            /// Builds the inspectable array GUI elements. Must be called at least once in order for the contents to be 
-            /// populated.
+            /// Creates a new inspectable array GUI object that displays the contents of the provided serializable property.
             /// </summary>
             /// <param name="title">Label to display on the list GUI title.</param>
             /// <param name="property">Serializable property referencing a single-dimensional array.</param>
@@ -112,18 +114,35 @@ namespace BansheeEditor
             /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
             ///                     nested containers whose backgrounds are overlaping. Also determines background style,
             ///                     depths divisible by two will use an alternate style.</param>
-            public void BuildGUI(LocString title, SerializableProperty property, GUILayout layout, int depth)
+            public static InspectableArrayGUI Create(LocString title, SerializableProperty property, GUILayout layout, int depth)
             {
-                this.property = property;
+                InspectableArrayGUI guiArray = new InspectableArrayGUI(title, property, layout, depth);
+                guiArray.BuildGUI();
 
-                object propertyValue = property.GetValue<object>();
-                if (propertyValue != null)
-                {
-                    SerializableArray array = property.GetArray();
-                    base.BuildGUI<InspectableArrayGUIRow>(title, false, array.GetLength(), layout, depth);
-                }
-                else
-                    base.BuildGUI<InspectableArrayGUIRow>(title, true, 0, layout, depth);
+                return guiArray;
+            }
+
+            /// <inheritdoc/>
+            protected override GUIListFieldRow CreateRow()
+            {
+                return new InspectableArrayGUIRow();
+            }
+
+            /// <inheritdoc/>
+            protected override bool IsNull()
+            {
+                Array array = property.GetValue<Array>();
+                return array == null;
+            }
+
+            /// <inheritdoc/>
+            protected override int GetNumRows()
+            {
+                Array array = property.GetValue<Array>();
+                if (array != null)
+                    return array.GetLength(0);
+
+                return 0;
             }
 
             /// <inheritdoc/>
@@ -142,13 +161,13 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected override void OnCreateButtonClicked()
+            protected override void CreateList()
             {
                 property.SetValue(property.CreateArrayInstance(new int[1] { 0 }));
             }
 
             /// <inheritdoc/>
-            protected override void OnResizeButtonClicked()
+            protected override void ResizeList()
             {
                 int size = guiSizeField.Value; // TODO - Support multi-rank arrays
 
@@ -164,13 +183,13 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected override void OnClearButtonClicked()
+            protected override void ClearList()
             {
                 property.SetValue<object>(null);
             }
 
             /// <inheritdoc/>
-            protected internal override void OnDeleteButtonClicked(int index)
+            protected internal override void DeleteElement(int index)
             {
                 Array array = property.GetValue<Array>();
 
@@ -191,7 +210,7 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected internal override void OnCloneButtonClicked(int index)
+            protected internal override void CloneElement(int index)
             {
                 SerializableArray array = property.GetArray();
 
@@ -217,7 +236,7 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected internal override void OnMoveUpButtonClicked(int index)
+            protected internal override void MoveUpElement(int index)
             {
                 Array array = property.GetValue<Array>();
 
@@ -231,7 +250,7 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected internal override void OnMoveDownButtonClicked(int index)
+            protected internal override void MoveDownElement(int index)
             {
                 Array array = property.GetValue<Array>();
 

+ 50 - 25
MBansheeEditor/Inspector/InspectableList.cs

@@ -13,7 +13,7 @@ namespace BansheeEditor
     {
         private object propertyValue; // TODO - This will unnecessarily hold references to the object
         private int numArrayElements;
-        private InspectableListGUI listGUIField = new InspectableListGUI();
+        private InspectableListGUI listGUIField;
 
         /// <summary>
         /// Creates a new inspectable list GUI for the specified property.
@@ -72,7 +72,7 @@ namespace BansheeEditor
         {
             GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
 
-            listGUIField.BuildGUI(title, property, arrayLayout, depth);
+            listGUIField = InspectableListGUI.Create(title, property, arrayLayout, depth);
         }
 
         /// <inheritdoc/>
@@ -101,12 +101,20 @@ namespace BansheeEditor
             /// <summary>
             /// Constructs a new empty inspectable list GUI.
             /// </summary>
-            public InspectableListGUI()
-            { }
+            /// <param name="title">Label to display on the list GUI title.</param>
+            /// <param name="property">Serializable property referencing a list.</param>
+            /// <param name="layout">Layout to which to append the list GUI elements to.</param>
+            /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
+            ///                     nested containers whose backgrounds are overlaping. Also determines background style,
+            ///                     depths divisible by two will use an alternate style.</param>
+            public InspectableListGUI(LocString title, SerializableProperty property, GUILayout layout, int depth)
+                : base(title, layout, depth)
+            {
+                this.property = property;
+            }
             
             /// <summary>
-            /// Builds the inspectable list GUI elements. Must be called at least once in order for the contents to be 
-            /// populated.
+            /// Creates a new inspectable list GUI object that displays the contents of the provided serializable property.
             /// </summary>
             /// <param name="title">Label to display on the list GUI title.</param>
             /// <param name="property">Serializable property referencing a list.</param>
@@ -114,26 +122,43 @@ namespace BansheeEditor
             /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
             ///                     nested containers whose backgrounds are overlaping. Also determines background style,
             ///                     depths divisible by two will use an alternate style.</param>
-            public void BuildGUI(LocString title, SerializableProperty property, GUILayout layout, int depth)
+            public static InspectableListGUI Create(LocString title, SerializableProperty property, GUILayout layout, int depth)
             {
-                this.property = property;
+                InspectableListGUI listGUI = new InspectableListGUI(title, property, layout, depth);
+                listGUI.BuildGUI();
 
-                object propertyValue = property.GetValue<object>();
-                if (propertyValue != null)
-                {
-                    SerializableList list = property.GetList();
-                    base.BuildGUI<InspectableListGUIRow>(title, false, list.GetLength(), layout, depth);
-                }
-                else
-                    base.BuildGUI<InspectableListGUIRow>(title, true, 0, layout, depth);
+                return listGUI;
+            }
+
+            /// <inheritdoc/>
+            protected override GUIListFieldRow CreateRow()
+            {
+                return new InspectableListGUIRow();
+            }
+
+            /// <inheritdoc/>
+            protected override bool IsNull()
+            {
+                IList list = property.GetValue<IList>();
+                return list == null;
+            }
+
+            /// <inheritdoc/>
+            protected override int GetNumRows()
+            {
+                IList list = property.GetValue<IList>();
+                if (list != null)
+                    return list.Count;
+
+                return 0;
             }
 
             /// <inheritdoc/>
             protected internal override object GetValue(int seqIndex)
             {
-                SerializableList array = property.GetList();
+                SerializableList list = property.GetList();
 
-                return array.GetProperty(seqIndex);
+                return list.GetProperty(seqIndex);
             }
 
             /// <inheritdoc/>
@@ -144,13 +169,13 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected override void OnCreateButtonClicked()
+            protected override void CreateList()
             {
                 property.SetValue(property.CreateListInstance(0));
             }
 
             /// <inheritdoc/>
-            protected override void OnResizeButtonClicked()
+            protected override void ResizeList()
             {
                 int size = guiSizeField.Value;
 
@@ -165,13 +190,13 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected override void OnClearButtonClicked()
+            protected override void ClearList()
             {
                 property.SetValue<object>(null);
             }
 
             /// <inheritdoc/>
-            protected internal override void OnDeleteButtonClicked(int index)
+            protected internal override void DeleteElement(int index)
             {
                 IList list = property.GetValue<IList>();
 
@@ -180,7 +205,7 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected internal override void OnCloneButtonClicked(int index)
+            protected internal override void CloneElement(int index)
             {
                 SerializableList serializableList = property.GetList();
                 IList list = property.GetValue<IList>();
@@ -190,7 +215,7 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected internal override void OnMoveUpButtonClicked(int index)
+            protected internal override void MoveUpElement(int index)
             {
                 IList list = property.GetValue<IList>();
 
@@ -204,7 +229,7 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected internal override void OnMoveDownButtonClicked(int index)
+            protected internal override void MoveDownElement(int index)
             {
                 IList list = property.GetValue<IList>();
 

+ 6 - 20
MBansheeEditor/Inspectors/FontInspector.cs

@@ -10,8 +10,8 @@ namespace BansheeEditor
     [CustomInspector(typeof(Font))]
     internal class FontInspector : Inspector
     {
-        private GUIArrayField<int> fontSizes = new GUIArrayField<int>();
-        private GUIArrayField<CharRange> charRanges = new GUIArrayField<CharRange>();
+        private GUIArrayField<int, FontSizeArrayRow> fontSizes;
+        private GUIArrayField<CharRange, CharRangeArrayRow> charRanges;
         private GUIToggleField antialiasingField;
         private GUIIntField dpiField;
         private GUIButton reimportButton;
@@ -75,27 +75,13 @@ namespace BansheeEditor
         {
             Layout.Clear();
 
-            fontSizes.BuildGUI<FontSizeArrayRow>(
+            fontSizes = GUIArrayField<int, FontSizeArrayRow>.Create(
                 new LocEdString("Font sizes"), importOptions.FontSizes, Layout);
-            fontSizes.OnChanged += x =>
-            {
-                int[] newFontSizes = x as int[];
-                importOptions.FontSizes = newFontSizes;
-
-                BuildGUI();
-                Refresh();
-            };
+            fontSizes.OnChanged += x => importOptions.FontSizes = x;
 
-            charRanges.BuildGUI<CharRangeArrayRow>(
+            charRanges = GUIArrayField<CharRange, CharRangeArrayRow>.Create(
                 new LocEdString("Character ranges"), importOptions.CharRanges, Layout);
-            charRanges.OnChanged += x =>
-            {
-                CharRange[] newRanges = x as CharRange[];
-                importOptions.CharRanges = newRanges;
-
-                BuildGUI();
-                Refresh();
-            };
+            charRanges.OnChanged += x => importOptions.CharRanges = x;
 
             antialiasingField = new GUIToggleField(new LocEdString("Antialiasing"));
             dpiField = new GUIIntField(new LocEdString("DPI"));

+ 24 - 17
MBansheeEditor/Inspectors/RenderableInspector.cs

@@ -12,11 +12,12 @@ namespace BansheeEditor
     {
         private GUIResourceField meshField;
         private GUIListBoxField layersField;
-        private GUIArrayField<Material> materialsField = new GUIArrayField<Material>();
+        private GUIArrayField<Material, MaterialArrayRow> materialsField;
         private List<MaterialParamGUI[]> materialParams = new List<MaterialParamGUI[]>();
 
         private ulong layersValue = 0;
         private Material[] materials;
+        private GUILayout materialsLayout;
 
         /// <inheritdoc/>
         protected internal override void Initialize()
@@ -31,26 +32,26 @@ namespace BansheeEditor
             if (renderable == null)
                 return;
 
-            bool rebuildGUI = false;
+            bool rebuildMaterialsGUI = false;
 
             Material[] newMaterials = renderable.Materials;
             if (newMaterials == null)
-                rebuildGUI = materials != null;
+                rebuildMaterialsGUI = materials != null;
             else
             {
                 if (materials == null)
-                    rebuildGUI = true;
+                    rebuildMaterialsGUI = true;
                 else
                 {
                     if (materials.Length != newMaterials.Length)
-                        rebuildGUI = true;
+                        rebuildMaterialsGUI = true;
                     else
                     {
                         for (int i = 0; i < materials.Length; i++)
                         {
                             if (materials[i] != newMaterials[i])
                             {
-                                rebuildGUI = true;
+                                rebuildMaterialsGUI = true;
                                 break;
                             }
                         }
@@ -58,8 +59,8 @@ namespace BansheeEditor
                 }
             }
 
-            if (rebuildGUI)
-                BuildGUI();
+            if (rebuildMaterialsGUI)
+                BuildMaterialsGUI();
 
             meshField.Value = renderable.Mesh;
 
@@ -105,14 +106,9 @@ namespace BansheeEditor
 
             layersValue = 0;
             materials = renderable.Materials;
-            materialsField.BuildGUI<MaterialArrayRow>(new LocEdString("Materials"), materials, Layout);
+            materialsField = GUIArrayField<Material, MaterialArrayRow>.Create(new LocEdString("Materials"), materials, Layout);
 
-            materialsField.OnChanged += x =>
-            {
-                renderable.Materials = (Material[])x;
-                BuildGUI();
-                Refresh();
-            };
+            materialsField.OnChanged += x => { renderable.Materials = x; };
             meshField.OnChanged += x => renderable.Mesh = x as Mesh;
             layersField.OnSelectionChanged += x =>
             {
@@ -125,6 +121,17 @@ namespace BansheeEditor
                 renderable.Layers = layers;
             };
 
+            materialsLayout = Layout.AddLayoutY();
+            BuildMaterialsGUI();
+        }
+
+        /// <summary>
+        /// Builds the portion of the GUI that displays details about individual materials.
+        /// </summary>
+        private void BuildMaterialsGUI()
+        {
+            materialsLayout.Clear();
+
             materialParams.Clear();
             if (materials != null)
             {
@@ -136,9 +143,9 @@ namespace BansheeEditor
                         continue;
                     }
 
-                    Layout.AddSpace(10);
+                    materialsLayout.AddSpace(10);
 
-                    MaterialParamGUI[] matParams = MaterialInspector.CreateMaterialGUI(materials[i], Layout);
+                    MaterialParamGUI[] matParams = MaterialInspector.CreateMaterialGUI(materials[i], materialsLayout);
                     materialParams.Add(matParams);
                 }
             }