Browse Source

Another GUI list/dictionary field refactor so outside code may get notified when changes to internal elements are made

BearishSun 10 years ago
parent
commit
d30c9e5941

+ 89 - 42
MBansheeEditor/GUI/GUIDictionaryField.cs

@@ -29,6 +29,7 @@ namespace BansheeEditor
         private object editOriginalKey;
 
         private State state;
+        private bool isModified;
 
         /// <summary>
         /// Constructs a new GUI dictionary.
@@ -67,11 +68,11 @@ namespace BansheeEditor
                 }
 
                 editRow = CreateRow();
-                editRow.BuildGUI(this, guiContentLayout, numRows, depth + 1);
+                editRow.Initialize(this, guiContentLayout, numRows, depth + 1);
                 editRow.Enabled = false;
 
                 for (int i = 0; i < numRows; i++)
-                    rows[i].BuildGUI(this, guiContentLayout, i, depth + 1);
+                    rows[i].Initialize(this, guiContentLayout, i, depth + 1);
             }
         }
 
@@ -186,27 +187,15 @@ namespace BansheeEditor
         }
 
         /// <summary>
-        /// Rebuilds GUI for all existing dictionary rows.
+        /// Destroys all rows and clears the row list.
         /// </summary>
-        private void BuildRows()
+        private void ClearRows()
         {
             foreach (var KVP in rows)
                 KVP.Value.Destroy();
 
             editRow.Destroy();
-
-            if (!IsNull())
-            {
-                editRow.BuildGUI(this, guiContentLayout, rows.Count, depth + 1);
-                editRow.Enabled = editRowIdx == rows.Count;
-
-                foreach (var KVP in rows)
-                    KVP.Value.BuildGUI(this, guiContentLayout, KVP.Key, depth + 1);
-            }
-            else
-            {
-                rows.Clear();
-            }
+            rows.Clear();
         }
 
         /// <summary>
@@ -221,18 +210,23 @@ namespace BansheeEditor
         /// <summary>
         /// Refreshes contents of all dictionary rows and checks if anything was modified.
         /// </summary>
-        public void Refresh()
+        /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
+        public InspectableState Refresh()
         {
-            bool anythingModified = false;
+            InspectableState state = InspectableState.NotModified;
             for (int i = 0; i < rows.Count; i++)
-                anythingModified |= rows[i].Refresh();
+                state |= rows[i].Refresh();
 
             if (editRow != null && editRow.Enabled)
-                anythingModified |= editRow.Refresh();
+                state |= editRow.Refresh();
+
+            if (isModified)
+            {
+                state |= InspectableState.ModifiedConfirm;
+                isModified = false;
+            }
 
-            // Note: I could just rebuild the individual rows but I'd have to remember their layout positions
-            if (anythingModified)
-                BuildRows();
+            return state;
         }
 
         /// <summary>
@@ -444,7 +438,11 @@ namespace BansheeEditor
         {
             CreateDictionary();
             UpdateHeaderGUI(false);
-            BuildRows();
+
+            editRow.Initialize(this, guiContentLayout, 0, depth + 1);
+            editRow.Enabled = false;
+
+            isModified = true;
         }
 
         /// <summary>
@@ -490,7 +488,9 @@ namespace BansheeEditor
         {
             DeleteDictionary();
             UpdateHeaderGUI(false);
-            BuildRows();
+            ClearRows();
+
+            isModified = true;
         }
 
         /// <summary>
@@ -532,13 +532,14 @@ namespace BansheeEditor
                     }
                 }
 
-                for (int i = newNumRows; i < oldNumRows; i++)
+                for (int i = oldNumRows; i >= newNumRows; i--)
                 {
                     rows[i].Destroy();
                     rows.Remove(i);
                 }
 
-                BuildRows();
+                editRow.SetIndex(newNumRows);
+                isModified = true;
             }
         }
 
@@ -717,11 +718,14 @@ namespace BansheeEditor
                 for (int i = 0; i < newNumRows; i++)
                     newKeys.Add(GetKey(i), i);
 
-                // Hidden dependency: BuildGUI must be called after all elements are 
+                // Hidden dependency: Initialize 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();
 
+                for (int i = oldNumRows; i < newNumRows; i++)
+                    rows[i].Initialize(this, guiContentLayout, i, depth + 1);
+
                 foreach (var KVP in oldKeys)
                 {
                     int newRowIdx;
@@ -742,7 +746,7 @@ namespace BansheeEditor
                     rows.Remove(i);
                 }
 
-                BuildRows();
+                editRow.SetIndex(newNumRows);
 
                 editKey = CreateKey();
                 editValue = CreateValue();
@@ -750,6 +754,8 @@ namespace BansheeEditor
                 editRowIdx = -1;
 
                 ToggleFoldout(isExpanded);
+                isModified = true;
+
                 return true;
             }
         }
@@ -1027,9 +1033,14 @@ namespace BansheeEditor
         private bool enabled = true;
         private bool editMode = false;
         private GUIDictionaryFieldBase parent;
+        private int rowIdx;
+        private int depth;
+        private InspectableState modifiedState;
 
-        protected int rowIdx;
-        protected int depth;
+        /// <summary>
+        /// Returns the depth at which the row is rendered.
+        /// </summary>
+        protected int Depth { get { return depth; } }
 
         /// <summary>
         /// Determines is the row currently being displayed.
@@ -1078,17 +1089,14 @@ namespace BansheeEditor
         }
 
         /// <summary>
-        /// (Re)creates all row GUI elements.
+        ///  Initializes the row and creates row GUI elements.
         /// </summary>
         /// <param name="parent">Parent array GUI object that the entry is contained in.</param>
         /// <param name="parentLayout">Parent layout that row GUI elements will be added to.</param>
         /// <param name="rowIdx">Sequential index of the row.</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 Initialize(GUIDictionaryFieldBase parent, GUILayout parentLayout, int rowIdx, int depth)
         {
-            if (rowLayout != null)
-                rowLayout.Destroy();
-
             this.parent = parent;
             this.rowIdx = rowIdx;
             this.depth = depth;
@@ -1098,6 +1106,26 @@ namespace BansheeEditor
             keyLayout = keyRowLayout.AddLayoutY();
             valueLayout = rowLayout.AddLayoutY();
 
+            BuildGUI();
+        }
+
+        /// <summary>
+        /// Changes the index of the dictionary element this row represents.
+        /// </summary>
+        /// <param name="seqIndex">Sequential index of the dictionary entry.</param>
+        internal void SetIndex(int seqIndex)
+        {
+            this.rowIdx = seqIndex;
+        }
+
+        /// <summary>
+        /// (Re)creates all row GUI elements.
+        /// </summary>
+        internal protected void BuildGUI()
+        {
+            keyLayout.Clear();
+            valueLayout.Clear();
+
             GUILayoutX externalTitleLayout = CreateKeyGUI(keyLayout);
             CreateValueGUI(valueLayout);
             if (localTitleLayout || (titleLayout != null && titleLayout == externalTitleLayout))
@@ -1136,8 +1164,6 @@ namespace BansheeEditor
             titleLayout.AddElement(editBtn);
 
             EditMode = editMode;
-
-            Refresh();
         }
 
         /// <summary>
@@ -1162,10 +1188,31 @@ namespace BansheeEditor
         /// <summary>
         /// Refreshes the GUI for the dictionary row and checks if anything was modified.
         /// </summary>
-        /// <returns>Determines should the field's GUI elements be updated due to modifications.</returns>
-        internal protected virtual bool Refresh()
+        /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
+        internal protected virtual InspectableState Refresh()
+        {
+            InspectableState oldState = modifiedState;
+            if (modifiedState.HasFlag(InspectableState.ModifiedConfirm))
+                modifiedState = InspectableState.NotModified;
+
+            return oldState;
+        }
+
+        /// <summary>
+        /// Marks the contents of the row as modified.
+        /// </summary>
+        protected void MarkAsModified()
+        {
+            modifiedState |= InspectableState.Modified;
+        }
+
+        /// <summary>
+        /// Confirms any queued modifications, signaling parent elements.
+        /// </summary>
+        protected void ConfirmModify()
         {
-            return false;
+            if (modifiedState.HasFlag(InspectableState.Modified))
+                modifiedState |= InspectableState.ModifiedConfirm;
         }
 
         /// <summary>

+ 111 - 37
MBansheeEditor/GUI/GUIListField.cs

@@ -24,6 +24,7 @@ namespace BansheeEditor
         protected LocString title;
 
         private State state;
+        private bool isModified;
 
         /// <summary>
         /// Constructs a new GUI list.
@@ -49,7 +50,7 @@ namespace BansheeEditor
 
             if (!IsNull())
             {
-                // Hidden dependency: BuildGUI must be called after all elements are 
+                // Hidden dependency: Initialize 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++)
@@ -59,7 +60,7 @@ namespace BansheeEditor
                 }
 
                 for (int i = 0; i < numRows; i++)
-                    rows[i].BuildGUI(this, guiContentLayout, i, depth + 1);
+                    rows[i].Initialize(this, guiContentLayout, i, depth + 1);
             }
         }
 
@@ -178,22 +179,26 @@ namespace BansheeEditor
         }
 
         /// <summary>
-        /// Rebuilds GUI for all existing list rows.
+        /// Updates list entry indices for all existing list rows.
         /// </summary>
-        private void BuildRows()
+        private void UpdateRows()
+        {
+            for (int i = 0; i < rows.Count; i++)
+            {
+                rows[i].SetIndex(i);
+                rows[i].BuildGUI();
+            }
+        }
+
+        /// <summary>
+        /// Destroys all rows and clears the row list.
+        /// </summary>
+        private void ClearRows()
         {
             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();
-            }
+            rows.Clear();
         }
 
         /// <summary>
@@ -208,15 +213,20 @@ namespace BansheeEditor
         /// <summary>
         /// Refreshes contents of all list rows and checks if anything was modified.
         /// </summary>
-        public void Refresh()
+        /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
+        public InspectableState Refresh()
         {
-            bool requiresRebuild = false;
+            InspectableState state = InspectableState.NotModified;
             for (int i = 0; i < rows.Count; i++)
-                requiresRebuild |= rows[i].Refresh();
+                state |= rows[i].Refresh();
 
-            // Note: I could just rebuild the individual rows but I'd have to remember their layout positions
-            if (requiresRebuild)
-                BuildRows();
+            if (isModified)
+            {
+                state |= InspectableState.ModifiedConfirm;
+                isModified = false;
+            }
+
+            return state;
         }
 
         /// <summary>
@@ -238,6 +248,8 @@ namespace BansheeEditor
 
             for (int i = 0; i < rows.Count; i++)
                 rows[i].Destroy();
+
+            rows.Clear();
         }
 
         /// <summary>
@@ -293,6 +305,7 @@ namespace BansheeEditor
             CreateList();
 
             UpdateHeaderGUI(false);
+            isModified = true;
         }
 
         /// <summary>
@@ -310,10 +323,17 @@ namespace BansheeEditor
                 rows.RemoveAt(i);
             }
 
-            for (int i = rows.Count; i < numRows; i++)
+            // Hidden dependency: Initialize must be called after all elements are 
+            // in the dictionary so we do it in two steps
+            int oldNumRows = rows.Count;
+            for (int i = oldNumRows; i < numRows; i++)
                 rows.Add(CreateRow());
 
-            BuildRows();
+            for (int i = oldNumRows; i < numRows; i++)
+                rows[i].Initialize(this, guiContentLayout, i, depth + 1);
+
+            UpdateRows();
+            isModified = true;
         }
 
         /// <summary>
@@ -324,7 +344,8 @@ namespace BansheeEditor
             ClearList();
 
             UpdateHeaderGUI(false);
-            BuildRows();
+            ClearRows();
+            isModified = true;
         }
 
         /// <summary>
@@ -342,7 +363,9 @@ namespace BansheeEditor
             }
 
             guiSizeField.Value = GetNumRows();
-            BuildRows();
+
+            UpdateRows();
+            isModified = true;
         }
 
         /// <summary>
@@ -358,7 +381,9 @@ namespace BansheeEditor
             rows.Add(row);
 
             guiSizeField.Value = GetNumRows();
-            BuildRows();
+
+            UpdateRows();
+            isModified = true;
         }
 
         /// <summary>
@@ -370,7 +395,8 @@ namespace BansheeEditor
         {
             MoveUpElement(index);
 
-            BuildRows();
+            UpdateRows();
+            isModified = true;
         }
 
         /// <summary>
@@ -382,7 +408,8 @@ namespace BansheeEditor
         {
             MoveDownElement(index);
 
-            BuildRows();
+            UpdateRows();
+            isModified = true;
         }
 
         /// <summary>
@@ -845,9 +872,19 @@ namespace BansheeEditor
         private GUILayoutX titleLayout;
         private bool localTitleLayout;
         private GUIListFieldBase parent;
+        private int seqIndex;
+        private int depth;
+        private InspectableState modifiedState;
 
-        protected int seqIndex;
-        protected int depth;
+        /// <summary>
+        /// Returns the sequential index of the list entry that this row displays.
+        /// </summary>
+        protected int SeqIndex { get { return seqIndex; } }
+
+        /// <summary>
+        /// Returns the depth at which the row is rendered.
+        /// </summary>
+        protected int Depth { get { return depth; } }
 
         /// <summary>
         /// Constructs a new list row object.
@@ -858,17 +895,14 @@ namespace BansheeEditor
         }
 
         /// <summary>
-        /// (Re)creates all row GUI elements.
+        /// Initializes the row and creates row GUI elements.
         /// </summary>
         /// <param name="parent">Parent array GUI object that the entry is contained in.</param>
         /// <param name="parentLayout">Parent layout that row GUI elements will be added to.</param>
-        /// <param name="seqIndex">Sequential index of the array entry.</param>
+        /// <param name="seqIndex">Sequential index of the list entry.</param>
         /// <param name="depth">Determines the depth at which the element is rendered.</param>
-        internal void BuildGUI(GUIListFieldBase parent, GUILayout parentLayout, int seqIndex, int depth)
+        internal void Initialize(GUIListFieldBase parent, GUILayout parentLayout, int seqIndex, int depth)
         {
-            if (rowLayout != null)
-                rowLayout.Destroy();
-
             this.parent = parent;
             this.seqIndex = seqIndex;
             this.depth = depth;
@@ -876,6 +910,25 @@ namespace BansheeEditor
             rowLayout = parentLayout.AddLayoutX();
             contentLayout = rowLayout.AddLayoutY();
 
+            BuildGUI();
+        }
+
+        /// <summary>
+        /// Changes the index of the list element this row represents.
+        /// </summary>
+        /// <param name="seqIndex">Sequential index of the list entry.</param>
+        internal void SetIndex(int seqIndex)
+        {
+            this.seqIndex = seqIndex;
+        }
+
+        /// <summary>
+        /// (Re)creates all row GUI elements.
+        /// </summary>
+        internal protected void BuildGUI()
+        {
+            contentLayout.Clear();
+
             GUILayoutX externalTitleLayout = CreateGUI(contentLayout);
             if (localTitleLayout || (titleLayout != null && titleLayout == externalTitleLayout))
                 return;
@@ -926,10 +979,31 @@ namespace BansheeEditor
         /// <summary>
         /// Refreshes the GUI for the list row and checks if anything was modified.
         /// </summary>
-        /// <returns>True if the row requires a rebuild, false otherwise.</returns>
-        internal protected virtual bool Refresh()
+        /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
+        internal protected virtual InspectableState Refresh()
+        {
+            InspectableState oldState = modifiedState;
+            if (modifiedState.HasFlag(InspectableState.ModifiedConfirm))
+                modifiedState = InspectableState.NotModified;
+
+            return oldState;
+        }
+
+        /// <summary>
+        /// Marks the contents of the row as modified.
+        /// </summary>
+        protected void MarkAsModified()
+        {
+            modifiedState |= InspectableState.Modified;
+        }
+
+        /// <summary>
+        /// Confirms any queued modifications, signaling parent elements.
+        /// </summary>
+        protected void ConfirmModify()
         {
-            return false;
+            if (modifiedState.HasFlag(InspectableState.Modified))
+                modifiedState |= InspectableState.ModifiedConfirm;
         }
 
         /// <summary>

+ 12 - 9
MBansheeEditor/Inspector/InspectableArray.cs

@@ -58,8 +58,7 @@ namespace BansheeEditor
             if (isModified)
                 Update(layoutIndex);
 
-            arrayGUIField.Refresh();
-            //isModified |= arrayGUIField.Refresh();
+            isModified |= arrayGUIField.Refresh().HasFlag(InspectableState.Modified);
 
             return isModified;
         }
@@ -283,7 +282,7 @@ namespace BansheeEditor
                 {
                     SerializableProperty property = GetValue<SerializableProperty>();
 
-                    field = CreateInspectable(seqIndex + ".", 0, depth + 1,
+                    field = CreateInspectable(SeqIndex + ".", 0, Depth + 1,
                         new InspectableFieldLayout(layout), property);
                 }
 
@@ -291,16 +290,20 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected internal override bool Refresh()
+            protected internal override InspectableState Refresh()
             {
-                if (field.IsModified())
+                //InspectableState state = field.Refresh(0);
+                InspectableState state = InspectableState.NotModified;
+                if (field.Refresh(0))
+                    state = InspectableState.Modified;
+
+                if(state.HasFlag(InspectableState.Modified))
                 {
-                    field.Refresh(0);
-                    return field.ShouldRebuildOnModify();
+                    if (field.ShouldRebuildOnModify())
+                        BuildGUI();
                 }
 
-                field.Refresh(0);
-                return false;
+                return state;
             }
         }
     }

+ 15 - 13
MBansheeEditor/Inspector/InspectableDictionary.cs

@@ -60,10 +60,9 @@ namespace BansheeEditor
             if (isModified)
                 Update(layoutIndex);
 
-            dictionaryGUIField.Refresh();
-            //isModified |= dictionaryGUIField.Refresh();
+            isModified |= dictionaryGUIField.Refresh().HasFlag(InspectableState.Modified);
 
-            return true;
+            return isModified;
         }
 
         /// <inheritdoc/>
@@ -278,7 +277,7 @@ namespace BansheeEditor
                 {
                     SerializableProperty property = GetKey<SerializableProperty>();
 
-                    fieldKey = CreateInspectable("Key", 0, depth + 1,
+                    fieldKey = CreateInspectable("Key", 0, Depth + 1,
                         new InspectableFieldLayout(layout), property);
                 }
 
@@ -292,23 +291,26 @@ namespace BansheeEditor
                 {
                     SerializableProperty property = GetValue<SerializableProperty>();
 
-                    fieldValue = CreateInspectable("Value", 0, depth + 1,
+                    fieldValue = CreateInspectable("Value", 0, Depth + 1,
                         new InspectableFieldLayout(layout), property);
                 }
             }
 
             /// <inheritdoc/>
-            protected internal override bool Refresh()
+            protected internal override InspectableState Refresh()
             {
-                bool rebuild = false;
-
-                if (fieldKey.IsModified())
-                    rebuild = fieldKey.ShouldRebuildOnModify();
+                //InspectableState state = fieldKey.Refresh(0) | fieldValue.Refresh(0);
+                InspectableState state = InspectableState.NotModified;
+                if (fieldKey.Refresh(0) || fieldValue.Refresh(0))
+                    state = InspectableState.Modified;
 
-                fieldKey.Refresh(0);
-                fieldValue.Refresh(0);
+                if (state.HasFlag(InspectableState.Modified))
+                {
+                    if (fieldKey.ShouldRebuildOnModify())
+                        BuildGUI();
+                }
 
-                return rebuild;
+                return state;
             }
         }
     }

+ 12 - 9
MBansheeEditor/Inspector/InspectableList.cs

@@ -60,8 +60,7 @@ namespace BansheeEditor
             if (isModified)
                 Update(layoutIndex);
 
-            listGUIField.Refresh();
-            //isModified |= listGUIField.Refresh();
+            isModified |= listGUIField.Refresh().HasFlag(InspectableState.Modified);
 
             return isModified;
         }
@@ -262,7 +261,7 @@ namespace BansheeEditor
                 {
                     SerializableProperty property = GetValue<SerializableProperty>();
 
-                    field = CreateInspectable(seqIndex + ".", 0, depth + 1,
+                    field = CreateInspectable(SeqIndex + ".", 0, Depth + 1,
                         new InspectableFieldLayout(layout), property);
                 }
 
@@ -270,16 +269,20 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            protected internal override bool Refresh()
+            protected internal override InspectableState Refresh()
             {
-                if (field.IsModified())
+                //InspectableState state = field.Refresh(0);
+                InspectableState state = InspectableState.NotModified;
+                if (field.Refresh(0))
+                    state = InspectableState.Modified;
+
+                if (state.HasFlag(InspectableState.Modified))
                 {
-                    field.Refresh(0);
-                    return field.ShouldRebuildOnModify();
+                    if (field.ShouldRebuildOnModify())
+                        BuildGUI();
                 }
 
-                field.Refresh(0);
-                return false;
+                return state;
             }
         }
     }

+ 14 - 0
MBansheeEditor/Inspector/InspectorUtility.cs

@@ -49,4 +49,18 @@ namespace BansheeEditor
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern Type Internal_GetCustomInspectable(Type type);
     }
+
+    /// <summary>
+    /// States an inspectable object can be in. Higher states override lower states.
+    /// </summary>
+    [Flags]
+    public enum InspectableState
+    {
+        /// <summary>Object was not modified this frame.</summary>
+        NotModified,
+        /// <summary>Object was modified but was not confirmed, therefore does not require saving.</summary>
+        Modified = 1,
+        /// <summary>Object was modified and confirmed, therefore it requires saving.</summary>
+        ModifiedConfirm = 2
+    }
 }

+ 22 - 7
MBansheeEditor/Inspectors/FontInspector.cs

@@ -150,19 +150,22 @@ namespace BansheeEditor
             protected override GUILayoutX CreateGUI(GUILayoutY layout)
             {
                 GUILayoutX titleLayout = layout.AddLayoutX();
-                sizeField = new GUIIntField(new LocEdString(seqIndex + ". "));
+                sizeField = new GUIIntField(new LocEdString(SeqIndex + ". "));
                 titleLayout.AddElement(sizeField);
 
-                sizeField.OnChanged += SetValue;
+                sizeField.OnChanged += x => { SetValue(x); MarkAsModified(); };
+                sizeField.OnFocusLost += ConfirmModify;
+                sizeField.OnConfirmed += ConfirmModify;
 
                 return titleLayout;
             }
 
             /// <inheritdoc/>
-            internal protected override bool Refresh()
+            internal protected override InspectableState Refresh()
             {
                 sizeField.Value = GetValue<int>();
-                return false;
+
+                return base.Refresh();
             }
         }
 
@@ -179,7 +182,7 @@ namespace BansheeEditor
             {
                 GUILayoutX titleLayout = layout.AddLayoutX();
 
-                rangeStartField = new GUIIntField(new LocEdString(seqIndex + ". Start"));
+                rangeStartField = new GUIIntField(new LocEdString(SeqIndex + ". Start"));
                 rangeEndField = new GUIIntField(new LocEdString("End"));
 
                 titleLayout.AddElement(rangeStartField);
@@ -190,6 +193,8 @@ namespace BansheeEditor
                     CharRange range = GetValue<CharRange>();
                     range.start = x;
                     SetValue(range);
+
+                    MarkAsModified();
                 };
 
                 rangeEndField.OnChanged += x =>
@@ -197,19 +202,29 @@ namespace BansheeEditor
                     CharRange range = GetValue<CharRange>();
                     range.end = x;
                     SetValue(range);
+
+                    MarkAsModified();
                 };
 
+                rangeStartField.OnFocusLost += ConfirmModify;
+                rangeStartField.OnConfirmed += ConfirmModify;
+
+                rangeEndField.OnFocusLost += ConfirmModify;
+                rangeEndField.OnConfirmed += ConfirmModify;
+
                 return titleLayout;
             }
 
             /// <inheritdoc/>
-            internal protected override bool Refresh()
+            internal protected override InspectableState Refresh()
             {
+                InspectableState state = InspectableState.NotModified;
+
                 CharRange newValue = GetValue<CharRange>();
                 rangeStartField.Value = newValue.start;
                 rangeEndField.Value = newValue.end;
 
-                return false;
+                return base.Refresh();
             }
         }
     }

+ 107 - 30
MBansheeEditor/Inspectors/GUISkinInspector.cs

@@ -120,7 +120,7 @@ namespace BansheeEditor
                 if(valueField == null)
                     valueField = new GUIElementStyleGUI();
 
-                valueField.BuildGUI(value, layout, depth);
+                valueField.BuildGUI(value, layout, Depth);
             }
 
             /// <inheritdoc/>
@@ -130,11 +130,10 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            internal protected override bool Refresh()
+            internal protected override InspectableState Refresh()
             {
                 keyField.Value = GetKey<string>();
-                valueField.Refresh();
-                return false;
+                return valueField.Refresh();
             }
         }
 
@@ -177,6 +176,7 @@ namespace BansheeEditor
 
             private GUIElementStyle style;
             private bool isExpanded;
+            private InspectableState modifiedState;
 
             /// <summary>
             /// Creates a new GUI element style GUI.
@@ -278,35 +278,68 @@ namespace BansheeEditor
                     isExpanded = x;
                 };
 
-                fontField.OnChanged += x => style.Font = (Font)x;
-                fontSizeField.OnChanged += x => style.FontSize = x;
-                horzAlignField.OnSelectionChanged += x => style.TextHorzAlign = (TextHorzAlign)x;
-                vertAlignField.OnSelectionChanged += x => style.TextVertAlign = (TextVertAlign)x;
-                imagePositionField.OnSelectionChanged += x => style.ImagePosition = (GUIImagePosition)x;
-                wordWrapField.OnChanged += x => style.WordWrap = x;
-
-                normalGUI.OnChanged += x => style.Normal = x;
-                hoverGUI.OnChanged += x => style.Hover = x;
-                activeGUI.OnChanged += x => style.Active = x;
-                focusedGUI.OnChanged += x => style.Focused = x;
-                normalOnGUI.OnChanged += x => style.NormalOn = x;
-                hoverOnGUI.OnChanged += x => style.HoverOn = x;
-                activeOnGUI.OnChanged += x => style.ActiveOn = x;
-                focusedOnGUI.OnChanged += x => style.FocusedOn = x;
-
-                borderGUI.OnChanged += x => style.Border = x;
-                marginsGUI.OnChanged += x => style.Margins = x;
-                contentOffsetGUI.OnChanged += x => style.ContentOffset = x;
-
-                fixedWidthField.OnChanged += x => { style.FixedWidth = x; };
+                fontField.OnChanged += x => { style.Font = (Font) x; MarkAsModified(); ConfirmModify(); };
+                fontSizeField.OnChanged += x => { style.FontSize = x; MarkAsModified(); };
+                fontSizeField.OnFocusLost += ConfirmModify;
+                fontSizeField.OnConfirmed += ConfirmModify;
+                horzAlignField.OnSelectionChanged += x =>
+                {
+                    style.TextHorzAlign = (TextHorzAlign)x; 
+                    MarkAsModified(); 
+                    ConfirmModify();
+                };
+                vertAlignField.OnSelectionChanged += x =>
+                {
+                    style.TextVertAlign = (TextVertAlign)x; 
+                    MarkAsModified(); 
+                    ConfirmModify();
+                };
+                imagePositionField.OnSelectionChanged += x =>
+                {
+                    style.ImagePosition = (GUIImagePosition)x; 
+                    MarkAsModified(); 
+                    ConfirmModify();
+                };
+                wordWrapField.OnChanged += x => { style.WordWrap = x; MarkAsModified(); ConfirmModify(); };
+
+                normalGUI.OnChanged += x => {style.Normal = x; MarkAsModified(); ConfirmModify(); };
+                hoverGUI.OnChanged += x => {style.Hover = x; MarkAsModified(); ConfirmModify(); };
+                activeGUI.OnChanged += x => {style.Active = x; MarkAsModified(); ConfirmModify(); };
+                focusedGUI.OnChanged += x => {style.Focused = x; MarkAsModified(); ConfirmModify(); };
+                normalOnGUI.OnChanged += x => {style.NormalOn = x; MarkAsModified(); ConfirmModify(); };
+                hoverOnGUI.OnChanged += x => {style.HoverOn = x; MarkAsModified(); ConfirmModify(); };
+                activeOnGUI.OnChanged += x => {style.ActiveOn = x; MarkAsModified(); ConfirmModify(); };
+                focusedOnGUI.OnChanged += x => { style.FocusedOn = x; MarkAsModified(); ConfirmModify(); };
+
+                borderGUI.OnChanged += x => { style.Border = x; MarkAsModified(); };
+                marginsGUI.OnChanged += x => { style.Margins = x; MarkAsModified(); };
+                contentOffsetGUI.OnChanged += x => { style.ContentOffset = x; MarkAsModified(); };
+
+                borderGUI.OnConfirmed += ConfirmModify;
+                marginsGUI.OnConfirmed += ConfirmModify;
+                contentOffsetGUI.OnConfirmed += ConfirmModify;
+
+                fixedWidthField.OnChanged += x => { style.FixedWidth = x; MarkAsModified(); ConfirmModify(); };
                 widthField.OnChanged += x => style.Width = x;
+                widthField.OnFocusLost += ConfirmModify;
+                widthField.OnConfirmed += ConfirmModify;
                 minWidthField.OnChanged += x => style.MinWidth = x;
+                minWidthField.OnFocusLost += ConfirmModify;
+                minWidthField.OnConfirmed += ConfirmModify;
                 maxWidthField.OnChanged += x => style.MaxWidth = x;
+                maxWidthField.OnFocusLost += ConfirmModify;
+                maxWidthField.OnConfirmed += ConfirmModify;
 
-                fixedHeightField.OnChanged += x => { style.FixedHeight = x; };
+                fixedHeightField.OnChanged += x => { style.FixedHeight = x; MarkAsModified(); ConfirmModify(); };
                 heightField.OnChanged += x => style.Height = x;
+                heightField.OnFocusLost += ConfirmModify;
+                heightField.OnConfirmed += ConfirmModify;
                 minHeightField.OnChanged += x => style.MinHeight = x;
+                minHeightField.OnFocusLost += ConfirmModify;
+                minHeightField.OnConfirmed += ConfirmModify;
                 maxHeightField.OnChanged += x => style.MaxHeight = x;
+                maxHeightField.OnFocusLost += ConfirmModify;
+                maxHeightField.OnConfirmed += ConfirmModify;
 
                 foldout.Value = isExpanded;
                 panel.Active = isExpanded;
@@ -315,10 +348,15 @@ namespace BansheeEditor
             /// <summary>
             /// Updates all GUI elements from the style if style changes.
             /// </summary>
-            public void Refresh()
+            /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
+            public InspectableState Refresh()
             {
+                InspectableState oldModifiedState = modifiedState;
+                if (modifiedState.HasFlag(InspectableState.ModifiedConfirm))
+                    modifiedState = InspectableState.NotModified;
+
                 if (style == null)
-                    return;
+                    return oldModifiedState;
 
                 fontField.Value = style.Font;
                 fontSizeField.Value = style.FontSize;
@@ -356,12 +394,31 @@ namespace BansheeEditor
                 heightField.Active = style.FixedHeight;
                 minHeightField.Active = !style.FixedHeight;
                 maxHeightField.Active = !style.FixedHeight;
+
+                return oldModifiedState;
+            }
+
+            /// <summary>
+            /// Marks the contents of the style as modified.
+            /// </summary>
+            private void MarkAsModified()
+            {
+                modifiedState |= InspectableState.Modified;
+            }
+
+            /// <summary>
+            /// Confirms any queued modifications, signaling parent element.
+            /// </summary>
+            private void ConfirmModify()
+            {
+                if (modifiedState.HasFlag(InspectableState.Modified))
+                    modifiedState |= InspectableState.ModifiedConfirm;
             }
 
             /// <summary>
             /// Creates GUI elements for editing/displaying <see cref="GUIElementStateStyle"/>
             /// </summary>
-            public class GUIElementStateStyleGUI
+            private class GUIElementStateStyleGUI
             {
                 private GUIToggle foldout;
                 private GUIResourceField textureField;
@@ -437,7 +494,7 @@ namespace BansheeEditor
             /// <summary>
             /// Creates GUI elements for editing/displaying <see cref="RectOffset"/>
             /// </summary>
-            public class RectOffsetGUI
+            private class RectOffsetGUI
             {
                 private GUIIntField offsetLeftField;
                 private GUIIntField offsetRightField;
@@ -449,6 +506,11 @@ namespace BansheeEditor
                 /// </summary>
                 public Action<RectOffset> OnChanged;
 
+                /// <summary>
+                /// Triggered when the user confirms input.
+                /// </summary>
+                public Action OnConfirmed;
+
                 /// <summary>
                 /// Creates a new rectangle offset GUI.
                 /// </summary>
@@ -505,6 +567,21 @@ namespace BansheeEditor
                         if (OnChanged != null)
                             OnChanged(offset);
                     };
+
+                    Action DoOnConfirmed = () =>
+                    {
+                        if (OnConfirmed != null)
+                            OnConfirmed();
+                    };
+
+                    offsetLeftField.OnConfirmed += DoOnConfirmed;
+                    offsetLeftField.OnFocusLost += DoOnConfirmed;
+                    offsetRightField.OnConfirmed += DoOnConfirmed;
+                    offsetRightField.OnFocusLost += DoOnConfirmed;
+                    offsetTopField.OnConfirmed += DoOnConfirmed;
+                    offsetTopField.OnFocusLost += DoOnConfirmed;
+                    offsetBottomField.OnConfirmed += DoOnConfirmed;
+                    offsetBottomField.OnFocusLost += DoOnConfirmed;
                 }
 
                 /// <summary>

+ 10 - 4
MBansheeEditor/Inspectors/RenderableInspector.cs

@@ -162,19 +162,25 @@ namespace BansheeEditor
             protected override GUILayoutX CreateGUI(GUILayoutY layout)
             {
                 GUILayoutX titleLayout = layout.AddLayoutX();
-                materialField = new GUIResourceField(typeof(Material), new LocEdString(seqIndex + ". "));
+                materialField = new GUIResourceField(typeof(Material), new LocEdString(SeqIndex + ". "));
                 titleLayout.AddElement(materialField);
 
-                materialField.OnChanged += SetValue;
+                materialField.OnChanged += x =>
+                {
+                    SetValue(x);
+                    MarkAsModified();
+                    ConfirmModify();
+                };
 
                 return titleLayout;
             }
 
             /// <inheritdoc/>
-            internal protected override bool Refresh()
+            internal protected override InspectableState Refresh()
             {
                 materialField.Value = GetValue<Material>();
-                return false;
+
+                return base.Refresh();
             }
         }
     }

+ 5 - 3
MBansheeEditor/Inspectors/StringTableInspector.cs

@@ -138,7 +138,9 @@ namespace BansheeEditor
                 valueField = new GUITextField(new LocEdString(value));
                 layout.AddElement(valueField);
 
-                valueField.OnChanged += SetValue;
+                valueField.OnChanged += x => { SetValue(x); MarkAsModified(); };
+                valueField.OnConfirmed += ConfirmModify;
+                valueField.OnFocusLost += ConfirmModify;
             }
 
             /// <inheritdoc/>
@@ -148,12 +150,12 @@ namespace BansheeEditor
             }
 
             /// <inheritdoc/>
-            internal protected override bool Refresh()
+            internal protected override InspectableState Refresh()
             {
                 keyField.Value = GetKey<string>();
                 valueField.Value = GetValue<string>();
 
-                return false;
+                return base.Refresh();
             }
         }
     }