Просмотр исходного кода

Refactored GUI dictionary so that it does not require a full rebuild every time a new entry is added or removed

BearishSun 10 лет назад
Родитель
Сommit
ba430a3650

+ 334 - 86
MBansheeEditor/GUI/GUIDictionaryField.cs

@@ -15,50 +15,75 @@ namespace BansheeEditor
 
 
         protected Dictionary<int, GUIDictionaryFieldRow> rows = new Dictionary<int, GUIDictionaryFieldRow>();
         protected Dictionary<int, GUIDictionaryFieldRow> rows = new Dictionary<int, GUIDictionaryFieldRow>();
         protected GUIDictionaryFieldRow editRow;
         protected GUIDictionaryFieldRow editRow;
+        protected GUILayoutY guiLayout;
         protected GUILayoutX guiChildLayout;
         protected GUILayoutX guiChildLayout;
         protected GUILayoutX guiTitleLayout;
         protected GUILayoutX guiTitleLayout;
         protected GUILayoutY guiContentLayout;
         protected GUILayoutY guiContentLayout;
         protected bool isExpanded;
         protected bool isExpanded;
         protected int depth;
         protected int depth;
+        protected LocString title;
 
 
         private int editRowIdx = -1;
         private int editRowIdx = -1;
         private object editKey;
         private object editKey;
         private object editValue;
         private object editValue;
         private object editOriginalKey;
         private object editOriginalKey;
 
 
-        /// <summary>
-        /// Constructs a new GUI dictionary.
-        /// </summary>
-        protected GUIDictionaryFieldBase()
-        { }
+        private State state;
 
 
         /// <summary>
         /// <summary>
-        /// Builds the dictionary GUI elements. Must be called at least once in order for the contents to be populated.
+        /// Constructs a new GUI dictionary.
         /// </summary>
         /// </summary>
-        /// <typeparam name="T">Type of rows that are used to handle GUI for individual dictionary elements.</typeparam>
-        /// <param name="title">Label to display on the dictionary 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 dictionary.</param>
+        ///  <param name="title">Label to display on the dictionary GUI title.</param>
         /// <param name="layout">Layout to which to append the list GUI elements to.</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
         /// <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,
         ///                     nested containers whose backgrounds are overlaping. Also determines background style,
         ///                     depths divisible by two will use an alternate style.</param>
         ///                     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 : GUIDictionaryFieldRow, new()
+        protected GUIDictionaryFieldBase(LocString title, GUILayout layout, int depth = 0)
         {
         {
-            Destroy();
-
+            this.title = title;
+            this.guiLayout = layout.AddLayoutY();
             this.depth = depth;
             this.depth = depth;
-            this.editKey = CreateKey();
-            this.editValue = CreateValue();
+        }
 
 
-            if (empty)
+        /// <summary>
+        /// Completely rebuilds the dictionary GUI elements.
+        /// </summary>
+        protected void BuildGUI()
+        {
+            editKey = CreateKey();
+            editValue = CreateValue();
+
+            UpdateHeaderGUI(true);
+
+            if (!IsNull())
             {
             {
-                rows.Clear();
+                // 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++)
+                {
+                    GUIDictionaryFieldRow newRow = CreateRow();
+                    rows.Add(i, newRow);
+                }
+
+                editRow = CreateRow();
+                editRow.BuildGUI(this, guiContentLayout, numRows, depth + 1);
+                editRow.Enabled = false;
 
 
-                guiChildLayout = null;
-                guiContentLayout = null;
-                guiTitleLayout = layout.AddLayoutX();
+                for (int i = 0; i < numRows; i++)
+                    rows[i].BuildGUI(this, guiContentLayout, i, depth + 1);
+            }
+        }
+
+        /// <summary>
+        /// Rebuilds the GUI dictionary 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(title));
                 guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
                 guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
@@ -67,8 +92,9 @@ namespace BansheeEditor
                 GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
                 GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
                 createBtn.OnClick += OnCreateButtonClicked;
                 createBtn.OnClick += OnCreateButtonClicked;
                 guiTitleLayout.AddElement(createBtn);
                 guiTitleLayout.AddElement(createBtn);
-            }
-            else
+            };
+
+            Action BuildFilledGUI = () =>
             {
             {
                 GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
                 GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
                 guiFoldout.Value = isExpanded;
                 guiFoldout.Value = isExpanded;
@@ -82,12 +108,12 @@ namespace BansheeEditor
                 GUIButton guiAddBtn = new GUIButton(addIcon, GUIOption.FixedWidth(30));
                 GUIButton guiAddBtn = new GUIButton(addIcon, GUIOption.FixedWidth(30));
                 guiAddBtn.OnClick += OnAddButtonClicked;
                 guiAddBtn.OnClick += OnAddButtonClicked;
 
 
-                guiTitleLayout = layout.AddLayoutX();
+                guiTitleLayout = guiLayout.AddLayoutX();
                 guiTitleLayout.AddElement(guiFoldout);
                 guiTitleLayout.AddElement(guiFoldout);
                 guiTitleLayout.AddElement(guiAddBtn);
                 guiTitleLayout.AddElement(guiAddBtn);
                 guiTitleLayout.AddElement(guiClearBtn);
                 guiTitleLayout.AddElement(guiClearBtn);
 
 
-                guiChildLayout = layout.AddLayoutX();
+                guiChildLayout = guiLayout.AddLayoutX();
                 guiChildLayout.AddSpace(IndentAmount);
                 guiChildLayout.AddSpace(IndentAmount);
 
 
                 GUIPanel guiContentPanel = guiChildLayout.AddPanel();
                 GUIPanel guiContentPanel = guiChildLayout.AddPanel();
@@ -109,27 +135,77 @@ namespace BansheeEditor
                 GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
                 GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
                 backgroundPanel.AddElement(inspectorContentBg);
                 backgroundPanel.AddElement(inspectorContentBg);
 
 
-                // Hidden dependency: BuildGUI must be called after all elements are 
-                // in the dictionary so we do it in two steps
-                for (int i = rows.Count; i < numRows; i++)
+                ToggleFoldout(isExpanded);
+            };
+
+            if (forceRebuild)
+            {
+                if (state != State.None)
+                    guiTitleLayout.Destroy();
+
+                if (state == State.Filled)
+                    guiChildLayout.Destroy();
+
+                state = State.None;
+            }
+
+            if (state == State.None)
+            {
+                if (!IsNull())
                 {
                 {
-                    GUIDictionaryFieldRow newRow = new T();
-                    rows.Add(i, newRow);
+                    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())
+                {
+                    guiTitleLayout.Destroy();
+                    guiChildLayout.Destroy();
+                    BuildEmptyGUI();
 
 
-                for (int i = numRows; i < rows.Count; i++)
-                    rows.Remove(i);
+                    state = State.Empty;
+                }
+            }
+        }
 
 
-                if(editRow == null)
-                    editRow = new T();
+        /// <summary>
+        /// Rebuilds GUI for all existing dictionary rows.
+        /// </summary>
+        private void BuildRows()
+        {
+            foreach (var KVP in rows)
+                KVP.Value.Destroy();
 
 
-                editRow.BuildGUI(this, guiContentLayout, numRows, depth + 1);
-                editRow.Enabled = false;
+            editRow.Destroy();
 
 
-                for (int i = 0; i < numRows; i++)
-                    rows[i].BuildGUI(this, guiContentLayout, i, depth + 1);
+            if (!IsNull())
+            {
+                editRow.BuildGUI(this, guiContentLayout, rows.Count, depth + 1);
+                editRow.Enabled = false;
 
 
-                ToggleFoldout(isExpanded);
+                foreach (var KVP in rows)
+                    KVP.Value.BuildGUI(this, guiContentLayout, KVP.Key, depth + 1);
+            }
+            else
+            {
+                rows.Clear();
             }
             }
         }
         }
 
 
@@ -145,11 +221,8 @@ namespace BansheeEditor
         /// <summary>
         /// <summary>
         /// Refreshes contents of all dictionary rows and checks if anything was modified.
         /// Refreshes contents of all dictionary rows and checks if anything was modified.
         /// </summary>
         /// </summary>
-        /// <returns>True if any entry in the list was modified, false otherwise.</returns>
-        public bool Refresh()
+        public void Refresh()
         {
         {
-            bool anythingModified = false;
-
             for (int i = 0; i < rows.Count; i++)
             for (int i = 0; i < rows.Count; i++)
             {
             {
                 if (rows[i].Refresh())
                 if (rows[i].Refresh())
@@ -162,7 +235,6 @@ namespace BansheeEditor
                     editRow.BuildGUI(this, guiContentLayout, rows.Count, depth + 1);
                     editRow.BuildGUI(this, guiContentLayout, rows.Count, depth + 1);
             }
             }
 
 
-            return anythingModified;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -170,23 +242,25 @@ namespace BansheeEditor
         /// </summary>
         /// </summary>
         public void Destroy()
         public void Destroy()
         {
         {
-            if (guiTitleLayout != null)
+            if (guiLayout != null)
             {
             {
-                guiTitleLayout.Destroy();
-                guiTitleLayout = null;
+                guiLayout.Destroy();
+                guiLayout = null;
             }
             }
 
 
-            if (guiChildLayout != null)
-            {
-                guiChildLayout.Destroy();
-                guiChildLayout = null;
-            }
+            guiLayout = null;
+            guiTitleLayout = null;
+            guiChildLayout = null;
 
 
             for (int i = 0; i < rows.Count; i++)
             for (int i = 0; i < rows.Count; i++)
                 rows[i].Destroy();
                 rows[i].Destroy();
 
 
+            rows.Clear();
+
             if (editRow != null)
             if (editRow != null)
                 editRow.Destroy();
                 editRow.Destroy();
+
+            editRow = null;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -208,6 +282,18 @@ namespace BansheeEditor
             return editRowIdx != -1;
             return editRowIdx != -1;
         }
         }
 
 
+        /// <summary>
+        /// Returns the number of rows in the dictionary.
+        /// </summary>
+        /// <returns>Number of rows in the dictionary.</returns>
+        protected abstract int GetNumRows();
+
+        /// <summary>
+        /// Checks is the dictionary instance not assigned.
+        /// </summary>
+        /// <returns>True if there is not dictionary instance.</returns>
+        protected abstract bool IsNull();
+
         /// <summary>
         /// <summary>
         /// Gets a value of an element at the specified index in the list. Also handles temporary edit fields.
         /// Gets a value of an element at the specified index in the list. Also handles temporary edit fields.
         /// </summary>
         /// </summary>
@@ -263,6 +349,12 @@ namespace BansheeEditor
             return GetKey(rowIdx);
             return GetKey(rowIdx);
         }
         }
 
 
+        /// <summary>
+        /// Creates a new dictionary row GUI.
+        /// </summary>
+        /// <returns>Object containing a dictionary row GUI.</returns>
+        protected abstract GUIDictionaryFieldRow CreateRow();
+
         /// <summary>
         /// <summary>
         /// Gets a key for a row at the specified index.
         /// Gets a key for a row at the specified index.
         /// </summary>
         /// </summary>
@@ -324,6 +416,16 @@ namespace BansheeEditor
         /// <returns>True if the key exists in the dictionary, false otherwise.</returns>
         /// <returns>True if the key exists in the dictionary, false otherwise.</returns>
         protected internal abstract bool Contains(object key);
         protected internal abstract bool Contains(object key);
 
 
+        /// <summary>
+        /// Creates a brand new dictionary with zero elements in the place of the current dictionary.
+        /// </summary>
+        protected abstract void CreateDictionary();
+
+        /// <summary>
+        /// Deletes the current dictionary object.
+        /// </summary>
+        protected abstract void DeleteDictionary();
+
         /// <summary>
         /// <summary>
         /// Hides or shows the dictionary rows.
         /// Hides or shows the dictionary rows.
         /// </summary>
         /// </summary>
@@ -340,7 +442,12 @@ namespace BansheeEditor
         /// Triggered when the user clicks on the create button on the title bar. Creates a brand new dictionary with zero
         /// Triggered when the user clicks on the create button on the title bar. Creates a brand new dictionary with zero
         /// elements in the place of the current dictionary.
         /// elements in the place of the current dictionary.
         /// </summary>
         /// </summary>
-        protected abstract void OnCreateButtonClicked();
+        protected void OnCreateButtonClicked()
+        {
+            CreateDictionary();
+            UpdateHeaderGUI(false);
+            BuildRows();
+        }
 
 
         /// <summary>
         /// <summary>
         /// Triggered when the user clicks on the add button on the title bar. Adds a new empty element to the dictionary.
         /// Triggered when the user clicks on the add button on the title bar. Adds a new empty element to the dictionary.
@@ -381,7 +488,12 @@ namespace BansheeEditor
         /// <summary>
         /// <summary>
         /// Triggered when the user clicks on the clear button on the title bar. Deletes the current dictionary object.
         /// Triggered when the user clicks on the clear button on the title bar. Deletes the current dictionary object.
         /// </summary>
         /// </summary>
-        protected abstract void OnClearButtonClicked();
+        protected void OnClearButtonClicked()
+        {
+            DeleteDictionary();
+            UpdateHeaderGUI(false);
+            BuildRows();
+        }
 
 
         /// <summary>
         /// <summary>
         /// Triggered when the user clicks on the delete button next to a dictionary entry. Deletes an element in the 
         /// Triggered when the user clicks on the delete button next to a dictionary entry. Deletes an element in the 
@@ -393,7 +505,43 @@ namespace BansheeEditor
             if (IsEditInProgress())
             if (IsEditInProgress())
                 DiscardChanges();
                 DiscardChanges();
             else
             else
+            {
+                // Remove the entry, but ensure the rows keep referencing the original keys (dictionaries have undefined
+                // order so we need to compare old vs. new elements to determine if any changed).
+                int oldNumRows = GetNumRows();
+                Dictionary<object, int> oldKeys = new Dictionary<object, int>();
+                for (int i = 0; i < oldNumRows; i++)
+                    oldKeys.Add(GetKey(i), i);
+
                 RemoveEntry(GetKey(rowIdx));
                 RemoveEntry(GetKey(rowIdx));
+
+                int newNumRows = GetNumRows();
+                Dictionary<object, int> newKeys = new Dictionary<object, int>();
+                for (int i = 0; i < newNumRows; i++)
+                    newKeys.Add(GetKey(i), i);
+
+                foreach (var KVP in oldKeys)
+                {
+                    int newRowIdx;
+                    if (newKeys.TryGetValue(KVP.Key, out newRowIdx))
+                    {
+                        if (KVP.Value != newRowIdx)
+                        {
+                            GUIDictionaryFieldRow temp = rows[KVP.Value];
+                            rows[KVP.Value] = rows[newRowIdx];
+                            rows[newRowIdx] = temp;
+                        }
+                    }
+                }
+
+                for (int i = newNumRows; i < oldNumRows; i++)
+                {
+                    rows[i].Destroy();
+                    rows.Remove(i);
+                }
+
+                BuildRows();
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -554,11 +702,50 @@ namespace BansheeEditor
                     rows[editRowIdx].EditMode = false;
                     rows[editRowIdx].EditMode = false;
                 }
                 }
 
 
+                // Add/remove the entry, but ensure the rows keep referencing the original keys (dictionaries have undefined
+                // order so we need to compare old vs. new elements to determine if any changed).
+                int oldNumRows = GetNumRows();
+                Dictionary<object, int> oldKeys = new Dictionary<object, int>();
+                for (int i = 0; i < oldNumRows; i++)
+                    oldKeys.Add(GetKey(i), i);
+
                 if (editOriginalKey != null) // Editing
                 if (editOriginalKey != null) // Editing
                     EditEntry(editOriginalKey, editKey, editValue);
                     EditEntry(editOriginalKey, editKey, editValue);
                 else // Adding/Cloning
                 else // Adding/Cloning
                     AddEntry(editKey, editValue);
                     AddEntry(editKey, editValue);
 
 
+                int newNumRows = GetNumRows();
+                Dictionary<object, int> newKeys = new Dictionary<object, int>();
+                for (int i = 0; i < newNumRows; i++)
+                    newKeys.Add(GetKey(i), i);
+
+                foreach (var KVP in oldKeys)
+                {
+                    int newRowIdx;
+                    if (newKeys.TryGetValue(KVP.Key, out newRowIdx))
+                    {
+                        if (KVP.Value != newRowIdx)
+                        {
+                            GUIDictionaryFieldRow temp = rows[KVP.Value];
+                            rows[KVP.Value] = rows[newRowIdx];
+                            rows[newRowIdx] = temp;
+                        }
+                    }
+                }
+
+                for (int i = newNumRows; i < oldNumRows; i++)
+                {
+                    rows[i].Destroy();
+                    rows.Remove(i);
+                }
+
+                // Hidden dependency: BuildGUI must be called after all elements are 
+                // in the dictionary so we do it in two steps
+                for (int i = oldNumRows; i < newNumRows; i++)
+                    rows[i] = CreateRow();
+
+                BuildRows();
+
                 editKey = CreateKey();
                 editKey = CreateKey();
                 editValue = CreateValue();
                 editValue = CreateValue();
                 editOriginalKey = null;
                 editOriginalKey = null;
@@ -576,6 +763,16 @@ namespace BansheeEditor
         {
         {
             if (IsEditInProgress())
             if (IsEditInProgress())
             {
             {
+                if (IsTemporaryRow(editRowIdx))
+                {
+                    editRow.EditMode = false;
+                    editRow.Enabled = false;
+                }
+                else
+                {
+                    rows[editRowIdx].EditMode = false;
+                }
+
                 editKey = CreateKey();
                 editKey = CreateKey();
                 editValue = CreateValue();
                 editValue = CreateValue();
                 editOriginalKey = null;
                 editOriginalKey = null;
@@ -585,6 +782,16 @@ namespace BansheeEditor
                 ToggleFoldout(isExpanded);
                 ToggleFoldout(isExpanded);
             }
             }
         }
         }
+
+        /// <summary>
+        /// Possible states dictionary GUI can be in.
+        /// </summary>
+        private enum State
+        {
+            None,
+            Empty,
+            Filled
+        }
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -593,7 +800,8 @@ namespace BansheeEditor
     /// </summary>
     /// </summary>
     /// <typeparam name="Key">Type of key used by the dictionary.</typeparam>
     /// <typeparam name="Key">Type of key used by the dictionary.</typeparam>
     /// <typeparam name="Value">Type of value stored in the dictionary.</typeparam>
     /// <typeparam name="Value">Type of value stored in the dictionary.</typeparam>
-    public class GUIDictionaryField<Key, Value> : GUIDictionaryFieldBase
+    /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual dictionary elements.</typeparam>
+    public class GUIDictionaryField<Key, Value, RowType> : GUIDictionaryFieldBase where RowType : GUIDictionaryFieldRow, new()
     {
     {
         public delegate int SortDictionaryDelegate(Key a, Key b);
         public delegate int SortDictionaryDelegate(Key a, Key b);
 
 
@@ -604,10 +812,15 @@ namespace BansheeEditor
         public Action<Dictionary<Key, Value>> OnChanged;
         public Action<Dictionary<Key, Value>> OnChanged;
 
 
         /// <summary>
         /// <summary>
-        /// Triggered when an element in the list has been changed.
+        /// Triggered when an element in the list has been added or changed.
         /// </summary>
         /// </summary>
         public Action<Key> OnValueChanged;
         public Action<Key> OnValueChanged;
 
 
+        /// <summary>
+        /// Triggered when an element in the dictionary has been removed.
+        /// </summary>
+        public Action<Key> OnValueRemoved;
+
         /// <summary>
         /// <summary>
         /// Optional method that will be used for sorting the elements in the dictionary.
         /// Optional method that will be used for sorting the elements in the dictionary.
         /// </summary>
         /// </summary>
@@ -622,10 +835,20 @@ namespace BansheeEditor
         private List<Key> orderedKeys = new List<Key>();
         private List<Key> orderedKeys = new List<Key>();
 
 
         /// <summary>
         /// <summary>
-        /// Constructs a new empty dictionary GUI.
+        /// Constructs a new GUI dictionary.
         /// </summary>
         /// </summary>
-        public GUIDictionaryField()
-        { }
+        /// <param name="title">Label to display on the dictionary GUI title.</param>
+        /// <param name="dictionary">Object containing the 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 GUIDictionaryField(LocString title, Dictionary<Key, Value> dictionary, GUILayout layout, int depth = 0)
+            : base(title, layout, depth)
+        {
+            this.dictionary = dictionary;
+            UpdateKeys();
+        }
 
 
         /// <summary>
         /// <summary>
         /// Builds the dictionary GUI elements. Must be called at least once in order for the contents to be populated.
         /// Builds the dictionary GUI elements. Must be called at least once in order for the contents to be populated.
@@ -637,17 +860,15 @@ namespace BansheeEditor
         /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
         /// <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,
         ///                     nested containers whose backgrounds are overlaping. Also determines background style,
         ///                     depths divisible by two will use an alternate style.</param>
         ///                     depths divisible by two will use an alternate style.</param>
-        public void BuildGUI<RowType>(LocString title, Dictionary<Key, Value> dictionary, 
+        public static GUIDictionaryField<Key, Value, RowType> Create(LocString title, Dictionary<Key, Value> dictionary, 
             GUILayout layout, int depth = 0)
             GUILayout layout, int depth = 0)
-            where RowType : GUIDictionaryFieldRow, new()
+
         {
         {
-            this.dictionary = dictionary;
-            UpdateKeys();
+            GUIDictionaryField<Key, Value, RowType> guiDictionary = new GUIDictionaryField<Key, Value, RowType>(
+                title, dictionary, layout, depth);
 
 
-            if (dictionary != null)
-                base.BuildGUI<RowType>(title, false, dictionary.Count, layout, depth);
-            else
-                base.BuildGUI<RowType>(title, true, 0, layout, depth);
+            guiDictionary.BuildGUI();
+            return guiDictionary;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -670,6 +891,27 @@ namespace BansheeEditor
             }
             }
         }
         }
 
 
+        /// <inheritdoc/>
+        protected override GUIDictionaryFieldRow CreateRow()
+        {
+            return new RowType();
+        }
+
+        /// <inheritdoc/>
+        protected override int GetNumRows()
+        {
+            if (dictionary != null)
+                return dictionary.Count;
+
+            return 0;
+        }
+
+        /// <inheritdoc/>
+        protected override bool IsNull()
+        {
+            return dictionary == null;
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected internal override object GetKey(int rowIdx)
         protected internal override object GetKey(int rowIdx)
         {
         {
@@ -703,8 +945,11 @@ namespace BansheeEditor
             dictionary.Remove((Key)oldKey);
             dictionary.Remove((Key)oldKey);
             dictionary[(Key)newKey] = (Value)value;
             dictionary[(Key)newKey] = (Value)value;
 
 
-            if (OnChanged != null)
-                OnChanged(dictionary);
+            if (OnValueRemoved != null)
+                OnValueRemoved((Key)oldKey);
+
+            if (OnValueChanged != null)
+                OnValueChanged((Key)newKey);
 
 
             UpdateKeys();
             UpdateKeys();
         }
         }
@@ -714,8 +959,8 @@ namespace BansheeEditor
         {
         {
             dictionary[(Key)key] = (Value)value;
             dictionary[(Key)key] = (Value)value;
 
 
-            if (OnChanged != null)
-                OnChanged(dictionary);
+            if (OnValueChanged != null)
+                OnValueChanged((Key)key);
 
 
             UpdateKeys();
             UpdateKeys();
         }
         }
@@ -725,8 +970,8 @@ namespace BansheeEditor
         {
         {
             dictionary.Remove((Key) key);
             dictionary.Remove((Key) key);
 
 
-            if (OnChanged != null)
-                OnChanged(dictionary);
+            if (OnValueRemoved != null)
+                OnValueRemoved((Key)key);
 
 
             UpdateKeys();
             UpdateKeys();
         }
         }
@@ -744,7 +989,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected override void OnCreateButtonClicked()
+        protected override void CreateDictionary()
         {
         {
             dictionary = new Dictionary<Key, Value>();
             dictionary = new Dictionary<Key, Value>();
 
 
@@ -755,7 +1000,7 @@ namespace BansheeEditor
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected override void OnClearButtonClicked()
+        protected override void DeleteDictionary()
         {
         {
             dictionary = null;
             dictionary = null;
 
 
@@ -780,6 +1025,7 @@ namespace BansheeEditor
         private GUIButton editBtn;
         private GUIButton editBtn;
         private bool localTitleLayout;
         private bool localTitleLayout;
         private bool enabled = true;
         private bool enabled = true;
+        private bool editMode = false;
         private GUIDictionaryFieldBase parent;
         private GUIDictionaryFieldBase parent;
 
 
         protected int rowIdx;
         protected int rowIdx;
@@ -817,6 +1063,8 @@ namespace BansheeEditor
                     deleteBtn.SetContent(deleteIcon);
                     deleteBtn.SetContent(deleteIcon);
                     editBtn.SetContent(editIcon);
                     editBtn.SetContent(editIcon);
                 }
                 }
+
+                editMode = value;
             }
             }
         }
         }
 
 
@@ -837,21 +1085,17 @@ namespace BansheeEditor
         /// <param name="depth">Determines the depth at which the element is rendered.</param>
         /// <param name="depth">Determines the depth at which the element is rendered.</param>
         internal void BuildGUI(GUIDictionaryFieldBase parent, GUILayout parentLayout, int rowIdx, int depth)
         internal void BuildGUI(GUIDictionaryFieldBase parent, GUILayout parentLayout, int rowIdx, int depth)
         {
         {
+            if (rowLayout != null)
+                rowLayout.Destroy();
+
             this.parent = parent;
             this.parent = parent;
             this.rowIdx = rowIdx;
             this.rowIdx = rowIdx;
             this.depth = depth;
             this.depth = depth;
 
 
-            if (rowLayout == null)
-                rowLayout = parentLayout.AddLayoutY();
-
-            if (keyRowLayout == null)
-                keyRowLayout = rowLayout.AddLayoutX();
-
-            if (keyLayout == null)
-                keyLayout = keyRowLayout.AddLayoutY();
-
-            if (valueLayout == null)
-                valueLayout = rowLayout.AddLayoutY();
+            rowLayout = parentLayout.AddLayoutY();
+            keyRowLayout = rowLayout.AddLayoutX();
+            keyLayout = keyRowLayout.AddLayoutY();
+            valueLayout = rowLayout.AddLayoutY();
 
 
             GUILayoutX externalTitleLayout = CreateKeyGUI(keyLayout);
             GUILayoutX externalTitleLayout = CreateKeyGUI(keyLayout);
             CreateValueGUI(valueLayout);
             CreateValueGUI(valueLayout);
@@ -889,6 +1133,10 @@ namespace BansheeEditor
             titleLayout.AddElement(deleteBtn);
             titleLayout.AddElement(deleteBtn);
             titleLayout.AddSpace(10);
             titleLayout.AddSpace(10);
             titleLayout.AddElement(editBtn);
             titleLayout.AddElement(editBtn);
+
+            EditMode = editMode;
+
+            Refresh();
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 45 - 19
MBansheeEditor/Inspector/InspectableDictionary.cs

@@ -13,7 +13,7 @@ namespace BansheeEditor
     {
     {
         private object propertyValue; // TODO - This will unnecessarily hold references to the object
         private object propertyValue; // TODO - This will unnecessarily hold references to the object
         private int numElements;
         private int numElements;
-        private InspectableDictionaryGUI dictionaryGUIField = new InspectableDictionaryGUI();
+        private InspectableDictionaryGUI dictionaryGUIField;
 
 
         /// <summary>
         /// <summary>
         /// Creates a new inspectable dictionary GUI for the specified property.
         /// Creates a new inspectable dictionary GUI for the specified property.
@@ -72,7 +72,7 @@ namespace BansheeEditor
         {
         {
             GUILayout dictionaryLayout = layout.AddLayoutY(layoutIndex);
             GUILayout dictionaryLayout = layout.AddLayoutY(layoutIndex);
 
 
-            dictionaryGUIField.BuildGUI(title, property, dictionaryLayout, depth);
+            dictionaryGUIField = InspectableDictionaryGUI.Create(title, property, dictionaryLayout, depth);
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -101,10 +101,20 @@ namespace BansheeEditor
             private List<object> orderedKeys = new List<object>();
             private List<object> orderedKeys = new List<object>();
 
 
             /// <summary>
             /// <summary>
-            /// Constructs a new empty dictionary GUI.
+            /// Constructs a new dictionary GUI.
             /// </summary>
             /// </summary>
-            public InspectableDictionaryGUI()
-            { }
+            /// <param name="title">Label to display on the list GUI title.</param>
+            /// <param name="property">Serializable property referencing a dictionary</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 InspectableDictionaryGUI(LocString title, SerializableProperty property, GUILayout layout, int depth = 0)
+            : base(title, layout, depth)
+        {
+            this.property = property;
+            UpdateKeys();
+        }
 
 
             /// <summary>
             /// <summary>
             /// Builds the inspectable dictionary GUI elements. Must be called at least once in order for the contents to 
             /// Builds the inspectable dictionary GUI elements. Must be called at least once in order for the contents to 
@@ -116,20 +126,13 @@ namespace BansheeEditor
             /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
             /// <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,
             ///                     nested containers whose backgrounds are overlaping. Also determines background style,
             ///                     depths divisible by two will use an alternate style.</param>
             ///                     depths divisible by two will use an alternate style.</param>
-            public void BuildGUI(LocString title, SerializableProperty property, GUILayout layout, int depth)
+            public static InspectableDictionaryGUI Create(LocString title, SerializableProperty property, GUILayout layout, 
+                int depth = 0)
             {
             {
-                this.property = property;
-
-                object propertyValue = property.GetValue<object>();
-                if (propertyValue != null)
-                {
-                    SerializableDictionary dictionary = property.GetDictionary();
-                    base.BuildGUI<InspectableDictionaryGUIRow>(title, false, dictionary.GetLength(), layout, depth);
-                }
-                else
-                    base.BuildGUI<InspectableDictionaryGUIRow>(title, true, 0, layout, depth);
+                InspectableDictionaryGUI guiDictionary = new InspectableDictionaryGUI(title, property, layout, depth);
 
 
-                UpdateKeys();
+                guiDictionary.BuildGUI();
+                return guiDictionary;
             }
             }
 
 
             /// <summary>
             /// <summary>
@@ -148,6 +151,29 @@ namespace BansheeEditor
                 }
                 }
             }
             }
 
 
+            /// <inheritdoc/>
+            protected override GUIDictionaryFieldRow CreateRow()
+            {
+                return new InspectableDictionaryGUIRow();
+            }
+
+            /// <inheritdoc/>
+            protected override int GetNumRows()
+            {
+                IDictionary dictionary = property.GetValue<IDictionary>();
+                if (dictionary != null)
+                    return dictionary.Count;
+
+                return 0;
+            }
+
+            /// <inheritdoc/>
+            protected override bool IsNull()
+            {
+                IDictionary dictionary = property.GetValue<IDictionary>();
+                return dictionary == null;
+            }
+
             /// <inheritdoc/>
             /// <inheritdoc/>
             protected internal override object GetKey(int rowIdx)
             protected internal override object GetKey(int rowIdx)
             {
             {
@@ -218,14 +244,14 @@ namespace BansheeEditor
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
-            protected override void OnCreateButtonClicked()
+            protected override void CreateDictionary()
             {
             {
                 property.SetValue(property.CreateDictionaryInstance());
                 property.SetValue(property.CreateDictionaryInstance());
                 UpdateKeys();
                 UpdateKeys();
             }
             }
 
 
             /// <inheritdoc/>
             /// <inheritdoc/>
-            protected override void OnClearButtonClicked()
+            protected override void DeleteDictionary()
             {
             {
                 property.SetValue<object>(null);
                 property.SetValue<object>(null);
                 UpdateKeys();
                 UpdateKeys();

+ 10 - 10
MBansheeEditor/Inspectors/GUISkinInspector.cs

@@ -10,7 +10,7 @@ namespace BansheeEditor
     [CustomInspector(typeof(GUISkin))]
     [CustomInspector(typeof(GUISkin))]
     public class GUISkinInspector : Inspector
     public class GUISkinInspector : Inspector
     {
     {
-        private GUIDictionaryField<string, GUIElementStyle> valuesField = new GUIDictionaryField<string, GUIElementStyle>();
+        private GUIDictionaryField<string, GUIElementStyle, GUIElementStyleEntry> valuesField;
 
 
         private Dictionary<string, GUIElementStyle> styles = new Dictionary<string, GUIElementStyle>();
         private Dictionary<string, GUIElementStyle> styles = new Dictionary<string, GUIElementStyle>();
 
 
@@ -42,7 +42,8 @@ namespace BansheeEditor
             foreach (var styleName in styleNames)
             foreach (var styleName in styleNames)
                 styles[styleName] = guiSkin.GetStyle(styleName);
                 styles[styleName] = guiSkin.GetStyle(styleName);
 
 
-            valuesField.BuildGUI<GUIElementStyleEntry>(new LocEdString("Styles"), styles, Layout);
+            valuesField = GUIDictionaryField<string, GUIElementStyle, GUIElementStyleEntry>.Create
+                (new LocEdString("Styles"), styles, Layout);
 
 
             valuesField.OnChanged += x =>
             valuesField.OnChanged += x =>
             {
             {
@@ -74,9 +75,6 @@ namespace BansheeEditor
                 }
                 }
 
 
                 EditorApplication.SetDirty(guiSkin);
                 EditorApplication.SetDirty(guiSkin);
-
-                BuildGUI();
-                Refresh();
             };
             };
 
 
             valuesField.OnValueChanged += x =>
             valuesField.OnValueChanged += x =>
@@ -85,6 +83,12 @@ namespace BansheeEditor
                 EditorApplication.SetDirty(guiSkin);
                 EditorApplication.SetDirty(guiSkin);
             };
             };
 
 
+            valuesField.OnValueRemoved += x =>
+            {
+                guiSkin.RemoveStyle(x);
+                EditorApplication.SetDirty(guiSkin);
+            };
+
             Layout.AddSpace(10);
             Layout.AddSpace(10);
         }
         }
 
 
@@ -113,7 +117,7 @@ namespace BansheeEditor
             {
             {
                 GUIElementStyle value = GetValue<GUIElementStyle>();
                 GUIElementStyle value = GetValue<GUIElementStyle>();
 
 
-                if (valueField == null)
+                if(valueField == null)
                     valueField = new GUIElementStyleGUI();
                     valueField = new GUIElementStyleGUI();
 
 
                 valueField.BuildGUI(value, layout, depth);
                 valueField.BuildGUI(value, layout, depth);
@@ -124,7 +128,6 @@ namespace BansheeEditor
             {
             {
                 keyField.Value = GetKey<string>();
                 keyField.Value = GetKey<string>();
                 valueField.Refresh();
                 valueField.Refresh();
-
                 return false;
                 return false;
             }
             }
         }
         }
@@ -194,9 +197,6 @@ namespace BansheeEditor
             {
             {
                 this.style = style;
                 this.style = style;
 
 
-                if (style == null)
-                    return;
-
                 short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1);
                 short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1);
                 string bgPanelStyle = depth % 2 == 0
                 string bgPanelStyle = depth % 2 == 0
                     ? EditorStyles.InspectorContentBgAlternate
                     ? EditorStyles.InspectorContentBgAlternate

+ 9 - 5
MBansheeEditor/Inspectors/StringTableInspector.cs

@@ -11,7 +11,7 @@ namespace BansheeEditor
     internal class StringTableInspector : Inspector
     internal class StringTableInspector : Inspector
     {
     {
         private GUIEnumField languageField;
         private GUIEnumField languageField;
-        private GUIDictionaryField<string, string> valuesField = new GUIDictionaryField<string,string>();
+        private GUIDictionaryField<string, string, StringTableEntry> valuesField;
 
 
         private Dictionary<string, string> strings = new Dictionary<string,string>();
         private Dictionary<string, string> strings = new Dictionary<string,string>();
 
 
@@ -60,7 +60,8 @@ namespace BansheeEditor
 
 
             Layout.AddElement(languageField);
             Layout.AddElement(languageField);
 
 
-            valuesField.BuildGUI<StringTableEntry>(new LocEdString("Strings"), strings, Layout);
+            valuesField = GUIDictionaryField<string, string, StringTableEntry>.Create(
+                new LocEdString("Strings"), strings, Layout);
 
 
             valuesField.OnChanged += x =>
             valuesField.OnChanged += x =>
             {
             {
@@ -92,9 +93,6 @@ namespace BansheeEditor
                 }
                 }
 
 
                 EditorApplication.SetDirty(stringTable);
                 EditorApplication.SetDirty(stringTable);
-
-                BuildGUI();
-                Refresh();
             };
             };
 
 
             valuesField.OnValueChanged += x =>
             valuesField.OnValueChanged += x =>
@@ -102,6 +100,12 @@ namespace BansheeEditor
                 stringTable.SetString(x, strings[x]);
                 stringTable.SetString(x, strings[x]);
                 EditorApplication.SetDirty(stringTable);
                 EditorApplication.SetDirty(stringTable);
             };
             };
+
+            valuesField.OnValueRemoved += x =>
+            {
+                stringTable.RemoveString(x);
+                EditorApplication.SetDirty(stringTable);
+            };
             
             
             Layout.AddSpace(10);
             Layout.AddSpace(10);
         }
         }