Browse Source

More work on animation editor, primarily on field display and selection
Fixed an issue with GUI where a destroyed element could attempted to be used by GUIUtility

BearishSun 9 years ago
parent
commit
e1daf513a5

+ 1 - 0
Source/MBansheeEditor/MBansheeEditor.csproj

@@ -51,6 +51,7 @@
     <Compile Include="Utility\SerializedObject.cs" />
     <Compile Include="Windows\AboutBox.cs" />
     <Compile Include="Windows\AnimationWindow.cs" />
+    <Compile Include="Windows\Animation\FieldSelectionWindow.cs" />
     <Compile Include="Windows\Animation\GUIAnimFieldDisplay.cs" />
     <Compile Include="Windows\Animation\GUICurveDrawing.cs" />
     <Compile Include="Windows\Animation\GUICurveEditor.cs" />

+ 43 - 0
Source/MBansheeEditor/Windows/Animation/FieldSelectionWindow.cs

@@ -0,0 +1,43 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+
+using System;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /** @addtogroup Library
+     *  @{
+     */
+
+    [DefaultSize(250, 350)]
+    internal class FieldSelectionWindow : DropDownWindow
+    {
+        private GUIFieldSelector guiFieldSelector;
+
+        public Action<string, SerializableProperty.FieldType> OnFieldSelected;
+
+        /// <summary>
+        /// Constructs the drop down window.
+        /// </summary>
+        public FieldSelectionWindow()
+        {
+            GUILayout layoutY = GUI.AddLayoutY();
+            layoutY.AddSpace(5);
+            GUILayout layoutX = layoutY.AddLayoutX();
+            layoutY.AddSpace(5);
+
+            layoutX.AddSpace(5);
+            GUILayout selectorLayout = layoutX.AddLayoutY();
+            layoutX.AddSpace(5);
+
+            guiFieldSelector = new GUIFieldSelector(selectorLayout, Selection.SceneObject);
+            guiFieldSelector.OnElementSelected += (so, comp, path, type) =>
+            {
+                OnFieldSelected?.Invoke(path, type);
+            };
+        }
+    }
+
+    /** @} */
+}

+ 250 - 52
Source/MBansheeEditor/Windows/Animation/GUIAnimFieldDisplay.cs

@@ -14,20 +14,37 @@ namespace BansheeEditor
     internal class GUIAnimFieldDisplay
     {
         private SceneObject root;
+        private int width;
+        private int height;
+
         private GUIScrollArea scrollArea;
         private List<string> paths = new List<string>();
 
         private GUIAnimFieldEntry[] fields;
+        private GUIAnimFieldLayouts layouts;
 
-        public GUIAnimFieldDisplay(GUILayout layout, SceneObject root)
+        public GUIAnimFieldDisplay(GUILayout layout, int width, int height, SceneObject root)
         {
             this.root = root;
 
             scrollArea = new GUIScrollArea();
             layout.AddElement(scrollArea);
+
+            SetSize(width, height);
         }
 
-        public Action<string[]> OnSelectionChanged;
+        public Action<string, bool> OnSelectionChanged;
+
+        public void SetSize(int width, int height)
+        {
+            this.width = width;
+            this.height = height;
+
+            scrollArea.SetWidth(width);
+            scrollArea.SetHeight(height);
+
+            Rebuild();
+        }
 
         public void SetFields(string[] paths)
         {
@@ -39,9 +56,25 @@ namespace BansheeEditor
 
         public void AddField(string path)
         {
-            paths.Add(path);
+            if (!paths.Contains(path))
+            {
+                paths.Add(path);
+                Rebuild();
+            }
+        }
 
-            Rebuild();
+        public void SetDisplayValues(GUIAnimFieldPathValue[] values)
+        {
+            for (int i = 0; i < fields.Length; i++)
+            {
+                string path = fields[i].Path;
+
+                for (int j = 0; j < values.Length; j++)
+                {
+                    if(path == values[j].path)
+                        fields[i].SetValue(values[j].value);
+                }
+            }
         }
 
         private SerializableProperty FindProperty(string path)
@@ -58,11 +91,14 @@ namespace BansheeEditor
             {
                 string entry = entries[pathIdx];
 
+                if (string.IsNullOrEmpty(entry))
+                    continue;
+
                 // Not a scene object, break
                 if (entry[0] != '!')
                     break;
 
-                if (pathIdx == 0)
+                if (so == null)
                     so = root;
                 else
                 {
@@ -78,7 +114,6 @@ namespace BansheeEditor
             if (so == null)
                 return null;
 
-            pathIdx++;
             if (pathIdx >= entries.Length)
                 return null;
 
@@ -163,6 +198,16 @@ namespace BansheeEditor
             if (paths == null || root == null)
                 return;
 
+            layouts = new GUIAnimFieldLayouts();
+            GUIPanel rootPanel = scrollArea.Layout.AddPanel();
+            GUIPanel mainPanel = rootPanel.AddPanel();
+            GUIPanel underlayPanel = rootPanel.AddPanel(1);
+            GUIPanel overlayPanel = rootPanel.AddPanel(-1);
+
+            layouts.main = mainPanel.AddLayoutY();
+            layouts.underlay = underlayPanel.AddLayoutY();
+            layouts.overlay = overlayPanel.AddLayoutY();
+
             fields = new GUIAnimFieldEntry[paths.Count];
             for (int i = 0; i < paths.Count; i++)
             {
@@ -172,57 +217,94 @@ namespace BansheeEditor
                     switch (property.Type)
                     {
                         case SerializableProperty.FieldType.Vector2:
-                            fields[i] = new GUIAnimVec2Entry(scrollArea.Layout, paths[i]);
+                            fields[i] = new GUIAnimVec2Entry(layouts, paths[i]);
                             break;
                         case SerializableProperty.FieldType.Vector3:
-                            fields[i] = new GUIAnimVec3Entry(scrollArea.Layout, paths[i]);
+                            fields[i] = new GUIAnimVec3Entry(layouts, paths[i]);
                             break;
                         case SerializableProperty.FieldType.Vector4:
-                            fields[i] = new GUIAnimVec4Entry(scrollArea.Layout, paths[i]);
+                            fields[i] = new GUIAnimVec4Entry(layouts, paths[i]);
                             break;
                         case SerializableProperty.FieldType.Color:
-                            fields[i] = new GUIAnimColorEntry(scrollArea.Layout, paths[i]);
+                            fields[i] = new GUIAnimColorEntry(layouts, paths[i]);
                             break;
                         case SerializableProperty.FieldType.Bool:
                         case SerializableProperty.FieldType.Int:
                         case SerializableProperty.FieldType.Float:
-                            fields[i] = new GUIAnimFieldEntry(scrollArea.Layout, paths[i]);
+                            fields[i] = new GUIAnimSimpleEntry(layouts, paths[i]);
                             break;
                     }
+
+                    if (fields[i] != null)
+                        fields[i].OnSelectionChanged += OnSelectionChanged;
                 }
                 else
                 {
-                    // TODO - Add special field type for missing properties
+                    fields[i] = new GUIAnimMissingEntry(layouts, paths[i]);
                 }
             }
+
+            layouts.main.AddFlexibleSpace();
+            layouts.underlay.AddFlexibleSpace();
+            layouts.overlay.AddFlexibleSpace();
         }
     }
 
-    internal class GUIAnimFieldEntry
+    internal class GUIAnimFieldLayouts
+    {
+        public GUILayout main;
+        public GUILayout underlay;
+        public GUILayout overlay;
+    }
+
+    internal struct GUIAnimFieldPathValue
+    {
+        public string path;
+        public object value;
+    }
+
+    internal abstract class GUIAnimFieldEntry
     {
         private const int MAX_PATH_LENGTH = 20;
         protected const int INDENT_AMOUNT = 10;
 
-        private GUIToggle toggle;
         protected string path;
+        private GUIToggle toggle;
+
+        private int entryHeight;
 
         public Action<string, bool> OnSelectionChanged;
 
-        public GUIAnimFieldEntry(GUILayout layout, string path)
+        public string Path { get { return path; } }
+
+        public GUIAnimFieldEntry(GUIAnimFieldLayouts layouts, string path)
         {
-            toggle = new GUIToggle(GetDisplayName(path), EditorStyles.SelectableLabel);
-            layout.AddElement(toggle);
+            this.path = path;
 
-            // TODO - Show current value (if not complex)
-            // TODO - Button to remove properties
+            GUILayoutX toggleLayout = layouts.main.AddLayoutX();
+            toggleLayout.AddSpace(15);
 
-            this.path = path;
+            toggle = new GUIToggle(GetDisplayName(path), EditorStyles.SelectableLabel, GUIOption.FlexibleWidth());
+            toggle.OnToggled += x => { OnSelectionChanged?.Invoke(path, x); };
+
+            toggleLayout.AddElement(toggle);
+
+            entryHeight = toggle.Bounds.height;
         }
 
         public virtual void Toggle(bool on)
-        { }
+        {
+            toggle.Active = on;
+        }
 
-        private static string GetDisplayName(string path)
+        public virtual void SetValue(object value) { }
+
+        protected int GetEntryHeight()
+        {
+            return entryHeight;
+        }
+
+        protected static string GetDisplayName(string path)
         {
             if (string.IsNullOrEmpty(path))
                 return "";
@@ -248,7 +330,7 @@ namespace BansheeEditor
                 return soName + "." + truncatedPropPath;
         }
 
-        private static void GetNames(string path, out string soName, out string compName, out string propertyPath)
+        protected static void GetNames(string path, out string soName, out string compName, out string propertyPath)
         {
             string[] entries = path.Split('/');
 
@@ -258,12 +340,15 @@ namespace BansheeEditor
             {
                 string entry = entries[pathIdx];
 
+                if (string.IsNullOrEmpty(entry))
+                    continue;
+
                 // Not a scene object, break
                 if (entry[0] != '!')
                     break;
             }
 
-            if (pathIdx >= entries.Length)
+            if (pathIdx == 0)
             {
                 soName = null;
                 compName = null;
@@ -272,9 +357,8 @@ namespace BansheeEditor
                 return;
             }
 
-            soName = entries[pathIdx].Substring(1, entries[pathIdx].Length - 1);
+            soName = entries[pathIdx - 1].Substring(1, entries[pathIdx - 1].Length - 1);
 
-            pathIdx++;
             if (pathIdx >= entries.Length)
             {
                 compName = null;
@@ -310,67 +394,181 @@ namespace BansheeEditor
         }
     }
 
-    internal class GUIAnimComplexEntry : GUIAnimFieldEntry
+    internal class GUIAnimSimpleEntry : GUIAnimFieldEntry
     {
-        private GUIToggle[] children;
-        private GUILayout childLayout;
+        private GUILabel valueDisplay;
+        private GUILayoutX underlayLayout;
+        private GUILabel overlaySpacing;
 
-        protected GUIAnimComplexEntry(GUILayout layout, string path, string[] childEntries)
-            : base(layout, path)
+        public GUIAnimSimpleEntry(GUIAnimFieldLayouts layouts, string path)
+            : base(layouts, path)
         {
-            childLayout = layout.AddLayoutX();
-            childLayout.AddSpace(INDENT_AMOUNT);
+            valueDisplay = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
+            underlayLayout = layouts.underlay.AddLayoutX();
+            underlayLayout.AddFlexibleSpace();
+            underlayLayout.AddElement(valueDisplay);
+            underlayLayout.AddSpace(50);
 
-            // TODO - Foldout to expand/collapse child layout
+            overlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
+            layouts.overlay.AddElement(overlaySpacing);
 
-            GUILayout indentLayout = childLayout.AddLayoutY();
+            // TODO - Alternating backgrounds
+        }
 
-            children = new GUIToggle[childEntries.Length];
-            for (int i = 0; i < childEntries.Length; i++)
-            {
-                int index = i;
+        public override void Toggle(bool on)
+        {
+            underlayLayout.Active = on;
+            overlaySpacing.Active = on;
 
-                children[i] = new GUIToggle(new LocEdString(childEntries[i]), EditorStyles.SelectableLabel);
-                children[i].OnToggled += x => { OnSelectionChanged?.Invoke(path + childEntries[index], x); };
+            base.Toggle(on);
+        }
 
-                indentLayout.AddElement(children[i]);
-            }
+        public override void SetValue(object value)
+        {
+            if (value == null)
+                return;
+
+            string strValue = value.ToString();
+            valueDisplay.SetContent(strValue);
+        }
+    }
+
+    internal class GUIAnimComplexEntry : GUIAnimFieldEntry
+    {
+        private GUILayout foldoutLayout;
+        private GUIToggle foldout;
+        private GUILabel underlaySpacing;
+
+        protected GUIAnimSimpleEntry[] children;
+
+        public GUIAnimComplexEntry(GUIAnimFieldLayouts layouts, string path, string[] childEntries)
+            : base(layouts, path)
+        {
+            foldout = new GUIToggle("", EditorStyles.Expand);
+            foldout.OnToggled += Toggle;
+            foldoutLayout = layouts.overlay.AddLayoutX();
+
+            foldoutLayout.AddElement(foldout);
+            foldoutLayout.AddFlexibleSpace();
+
+            underlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
+            layouts.underlay.AddElement(underlaySpacing);
+
+            children = new GUIAnimSimpleEntry[childEntries.Length];
+            for (int i = 0; i < childEntries.Length; i++)
+                children[i] = new GUIAnimSimpleEntry(layouts, path + childEntries[i]);
 
             Toggle(false);
         }
 
         public override void Toggle(bool on)
         {
-            childLayout.Active = on;
+            foreach(var child in children)
+                child.Toggle(on);
         }
     }
 
     internal class GUIAnimVec2Entry : GUIAnimComplexEntry
     {
-        public GUIAnimVec2Entry(GUILayout layout, string path)
-            : base(layout, path,  new[] { ".x", ".y" })
+        public GUIAnimVec2Entry(GUIAnimFieldLayouts layouts, string path)
+            : base(layouts, path,  new[] { ".x", ".y" })
         { }
+
+        public override void SetValue(object value)
+        {
+            if (value == null)
+                return;
+
+            Vector2 vector = (Vector2)value;
+            children[0].SetValue(vector.x);
+            children[1].SetValue(vector.y);
+        }
     }
 
     internal class GUIAnimVec3Entry : GUIAnimComplexEntry
     {
-        public GUIAnimVec3Entry(GUILayout layout, string path)
-            : base(layout, path, new[] { ".x", ".y", ".z" })
+        public GUIAnimVec3Entry(GUIAnimFieldLayouts layouts, string path)
+            : base(layouts, path, new[] { ".x", ".y", ".z" })
         { }
+
+        public override void SetValue(object value)
+        {
+            if (value == null)
+                return;
+
+            Vector3 vector = (Vector3)value;
+            children[0].SetValue(vector.x);
+            children[1].SetValue(vector.y);
+            children[2].SetValue(vector.z);
+        }
     }
 
     internal class GUIAnimVec4Entry : GUIAnimComplexEntry
     {
-        public GUIAnimVec4Entry(GUILayout layout, string path)
-            : base(layout, path,  new[] { ".x", ".y", ".z", ".w" })
+        public GUIAnimVec4Entry(GUIAnimFieldLayouts layouts, string path)
+            : base(layouts, path,  new[] { ".x", ".y", ".z", ".w" })
         { }
+
+        public override void SetValue(object value)
+        {
+            if (value == null)
+                return;
+
+            Vector4 vector = (Vector4)value;
+            children[0].SetValue(vector.x);
+            children[1].SetValue(vector.y);
+            children[2].SetValue(vector.z);
+            children[3].SetValue(vector.w);
+        }
     }
 
     internal class GUIAnimColorEntry : GUIAnimComplexEntry
     {
-        public GUIAnimColorEntry(GUILayout layout, string path)
-            : base(layout, path, new[] { ".r", ".g", ".b", ".a" })
+        public GUIAnimColorEntry(GUIAnimFieldLayouts layouts, string path)
+            : base(layouts, path, new[] { ".r", ".g", ".b", ".a" })
         { }
+
+        public override void SetValue(object value)
+        {
+            if (value == null)
+                return;
+
+            Color color = (Color)value;
+            children[0].SetValue(color.r);
+            children[1].SetValue(color.g);
+            children[2].SetValue(color.b);
+            children[3].SetValue(color.a);
+        }
+    }
+
+    internal class GUIAnimMissingEntry : GUIAnimFieldEntry
+    {
+        private GUILabel missingLabel;
+        private GUILayoutX underlayLayout;
+        private GUILabel overlaySpacing;
+
+        public GUIAnimMissingEntry(GUIAnimFieldLayouts layouts, string path)
+            : base(layouts, path)
+        {
+            missingLabel = new GUILabel("Missing property!", GUIOption.FixedHeight(GetEntryHeight()));
+            underlayLayout = layouts.underlay.AddLayoutX();
+            underlayLayout.AddFlexibleSpace();
+            underlayLayout.AddElement(missingLabel);
+            underlayLayout.AddSpace(50);
+
+            overlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
+            layouts.overlay.AddElement(overlaySpacing);
+
+            // TODO - Alternating backgrounds
+        }
+
+        public override void Toggle(bool on)
+        {
+            underlayLayout.Active = on;
+            overlaySpacing.Active = on;
+
+            base.Toggle(on);
+        }
     }
 
     /** @} */

+ 1 - 1
Source/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -587,7 +587,7 @@ namespace BansheeEditor
             int heightOffset = height/2; // So that y = 0 is at center of canvas
 
             KeyFrame[] keyframes = curve.KeyFrames;
-            if (keyframes.Length < 0)
+            if (keyframes.Length <= 0)
                 return;
 
             // Draw start line

+ 4 - 9
Source/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -99,14 +99,9 @@ namespace BansheeEditor
 
             guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT);
             guiSidebar.SetRange(-10.0f, 10.0f);
-
-            EditorInput.OnPointerPressed += OnPointerPressed;
-            EditorInput.OnPointerMoved += OnPointerMoved;
-            EditorInput.OnPointerReleased += OnPointerReleased;
-            EditorInput.OnButtonUp += OnButtonUp;
         }
 
-        private void OnPointerPressed(PointerEvent ev)
+        internal void OnPointerPressed(PointerEvent ev)
         {
             if (ev.IsUsed)
                 return;
@@ -194,7 +189,7 @@ namespace BansheeEditor
             }
         }
 
-        private void OnPointerMoved(PointerEvent ev)
+        internal void OnPointerMoved(PointerEvent ev)
         {
             if (ev.Button != PointerButton.Left)
                 return;
@@ -334,7 +329,7 @@ namespace BansheeEditor
             }
         }
 
-        private void OnPointerReleased(PointerEvent ev)
+        internal void OnPointerReleased(PointerEvent ev)
         {
             isPointerHeld = false;
             isDragInProgress = false;
@@ -342,7 +337,7 @@ namespace BansheeEditor
             isMousePressedOverTangent = false;
         }
 
-        private void OnButtonUp(ButtonEvent ev)
+        internal void OnButtonUp(ButtonEvent ev)
         {
             if(ev.Button == ButtonCode.Delete)
                 DeleteSelectedKeyframes();

+ 23 - 14
Source/MBansheeEditor/Windows/Animation/GUIFieldSelector.cs

@@ -61,7 +61,7 @@ namespace BansheeEditor
         /// 
         /// For example: !My Scene Object/:Camera/path/to/field
         /// </summary>
-        public Action<SceneObject, Component, string> OnElementSelected;
+        public Action<SceneObject, Component, string, SerializableProperty.FieldType> OnElementSelected;
 
         /// <summary>
         /// Creates a new GUIFieldSelector and registers its GUI elements in the provided layout.
@@ -88,7 +88,11 @@ namespace BansheeEditor
                 return;
 
             rootElement.so = rootSO;
+            rootElement.childLayout = mainLayout;
+            rootElement.indentLayout = null;
+
             AddSceneObjectRows(rootElement);
+            mainLayout.AddFlexibleSpace();
         }
 
         /// <summary>
@@ -107,7 +111,7 @@ namespace BansheeEditor
             SpriteTexture compIcon = EditorBuiltin.GetEditorIcon(EditorIcon.Component);
 
             // Add transform
-            parent.children[0] = AddFoldoutRow(mainLayout, soIcon, "Transform", parent.so, 
+            parent.children[0] = AddFoldoutRow(parent.childLayout, soIcon, "Transform", parent.so, 
                 null, parent.path + "/" + soName, ToggleTransformFoldout);
 
             // Add components
@@ -123,13 +127,16 @@ namespace BansheeEditor
                 
                 string name = childComponent.GetType().Name;
                 string path = parent.path + "/" + soName + "/:" + name;
-                parent.children[i + 1] = AddFoldoutRow(mainLayout, compIcon, name, parent.so, childComponent, path,
+                parent.children[i + 1] = AddFoldoutRow(parent.childLayout, compIcon, name, parent.so, childComponent, path,
                     toggleCallback);
             }
 
             // Add children
-            parent.children[parent.children.Length - 1] = AddFoldoutRow(mainLayout, soIcon, "Children", parent.so, null,
-                parent.path + "/" + soName, ToggleChildFoldout);
+            if (parent.so.GetNumChildren() > 0)
+            {
+                parent.children[parent.children.Length - 1] = AddFoldoutRow(parent.childLayout, soIcon, "Children",
+                    parent.so, null, parent.path + "/" + soName, ToggleChildFoldout);
+            }
         }
 
         /// <summary>
@@ -156,7 +163,7 @@ namespace BansheeEditor
                     case SerializableProperty.FieldType.Vector2:
                     case SerializableProperty.FieldType.Vector3:
                     case SerializableProperty.FieldType.Vector4:
-                        elements.Add(AddFieldRow(parent.childLayout, field.Name, parent.so, parent.comp, propertyPath));
+                        elements.Add(AddFieldRow(parent.childLayout, field.Name, parent.so, parent.comp, propertyPath, field.Type));
                         break;
                     case SerializableProperty.FieldType.Object:
                         Action<Element, bool> toggleCallback = 
@@ -184,8 +191,9 @@ namespace BansheeEditor
         /// <param name="component">Parent component of the field. Can be null if field belongs to <see cref="SceneObject"/>.
         ///                         </param>
         /// <param name="path">Slash separated path to the field from its parent object.</param>
+        /// <param name="type">Data type stored in the field.</param>
         /// <returns>Element object storing all information about the added field.</returns>
-        private Element AddFieldRow(GUILayout layout, string name, SceneObject so, Component component, string path)
+        private Element AddFieldRow(GUILayout layout, string name, SceneObject so, Component component, string path, SerializableProperty.FieldType type)
         {
             Element element = new Element(so, component, path);
 
@@ -195,7 +203,7 @@ namespace BansheeEditor
             elementLayout.AddElement(label);
 
             GUIButton selectBtn = new GUIButton(new LocEdString("Select"));
-            selectBtn.OnClick += () => { DoOnElementSelected(element); };
+            selectBtn.OnClick += () => { DoOnElementSelected(element, type); };
 
             elementLayout.AddFlexibleSpace();
             elementLayout.AddElement(selectBtn);
@@ -226,7 +234,7 @@ namespace BansheeEditor
             GUILayoutY elementLayout = layout.AddLayoutY();
             GUILayoutX foldoutLayout = elementLayout.AddLayoutX();
 
-            element.toggle = new GUIToggle(EditorStyles.Expand);
+            element.toggle = new GUIToggle("", EditorStyles.Expand);
             element.toggle.OnToggled += x => toggleCallback(element, x);
 
             foldoutLayout.AddElement(element.toggle);
@@ -267,9 +275,9 @@ namespace BansheeEditor
             {
                 parent.children = new Element[3];
 
-                parent.children[0] = AddFieldRow(parent.childLayout, "Position", parent.so, null, parent.path + "/Position");
-                parent.children[1] = AddFieldRow(parent.childLayout, "Rotation", parent.so, null, parent.path + "/Rotation");
-                parent.children[2] = AddFieldRow(parent.childLayout, "Scale", parent.so, null, parent.path + "/Scale");
+                parent.children[0] = AddFieldRow(parent.childLayout, "Position", parent.so, null, parent.path + "/Position", SerializableProperty.FieldType.Vector3);
+                parent.children[1] = AddFieldRow(parent.childLayout, "Rotation", parent.so, null, parent.path + "/Rotation", SerializableProperty.FieldType.Vector4);
+                parent.children[2] = AddFieldRow(parent.childLayout, "Scale", parent.so, null, parent.path + "/Scale", SerializableProperty.FieldType.Vector3);
             }
         }
 
@@ -344,10 +352,11 @@ namespace BansheeEditor
         /// Triggered when the user selects a field.
         /// </summary>
         /// <param name="element">Row element for the field that was selected.</param>
-        private void DoOnElementSelected(Element element)
+        /// <param name="type">Data type stored in the field.</param>
+        private void DoOnElementSelected(Element element, SerializableProperty.FieldType type)
         {
             if (OnElementSelected != null)
-                OnElementSelected(element.so, element.comp, element.path);
+                OnElementSelected(element.so, element.comp, element.path, type);
         }
     }
 

+ 248 - 16
Source/MBansheeEditor/Windows/AnimationWindow.cs

@@ -13,19 +13,30 @@ namespace BansheeEditor
     /// <summary>
     /// Displays animation curve editor window.
     /// </summary>
+    [DefaultSize(900, 500)]
     internal class AnimationWindow : EditorWindow
     {
+        private const int FIELD_DISPLAY_WIDTH = 200;
+
+        private bool isInitialized;
         private GUIFloatField lengthField;
         private GUIIntField fpsField;
         private GUIFloatField yRangeField;
         private GUIButton addKeyframeBtn;
 
+        private GUIButton addPropertyBtn;
+        private GUIButton delPropertyBtn;
+
         private GUILayout buttonLayout;
         private int buttonLayoutHeight;
 
         private GUIPanel editorPanel;
+        private GUIAnimFieldDisplay guiFieldDisplay;
         private GUICurveEditor guiCurveEditor;
 
+        private Dictionary<string, EdAnimationCurve> curves = new Dictionary<string, EdAnimationCurve>();
+        private List<string> selectedFields = new List<string>();
+
         /// <summary>
         /// Opens the animation window.
         /// </summary>
@@ -43,15 +54,101 @@ namespace BansheeEditor
 
         private void OnInitialize()
         {
+            Selection.OnSelectionChanged += OnSelectionChanged;
+            EditorInput.OnPointerPressed += OnPointerPressed;
+            EditorInput.OnPointerMoved += OnPointerMoved;
+            EditorInput.OnPointerReleased += OnPointerReleased;
+            EditorInput.OnButtonUp += OnButtonUp;
+
+            Rebuild();
+        }
+
+        private void OnEditorUpdate()
+        {
+
+        }
+
+        private void OnDestroy()
+        {
+            Selection.OnSelectionChanged -= OnSelectionChanged;
+            EditorInput.OnPointerPressed -= OnPointerPressed;
+            EditorInput.OnPointerMoved -= OnPointerMoved;
+            EditorInput.OnPointerReleased -= OnPointerReleased;
+            EditorInput.OnButtonUp -= OnButtonUp;
+        }
+
+        protected override void WindowResized(int width, int height)
+        {
+            if (!isInitialized)
+                return;
+
+            guiFieldDisplay.SetSize(width, height - buttonLayoutHeight*2);
+
+            int curveEditorWidth = Math.Max(0, width - FIELD_DISPLAY_WIDTH);
+            guiCurveEditor.SetSize(curveEditorWidth, height - buttonLayoutHeight);
+            guiCurveEditor.Redraw();
+        }
+
+        private void Rebuild()
+        {
+            GUI.Clear();
+            selectedFields.Clear();
+            curves.Clear();
+            isInitialized = false;
+
+            SceneObject selectedSO = Selection.SceneObject;
+            if (selectedSO == null)
+            {
+                GUILabel warningLbl = new GUILabel(new LocEdString("Select an object to animate in the Hierarchy or Scene windows."));
+
+                GUILayoutY vertLayout = GUI.AddLayoutY();
+                vertLayout.AddFlexibleSpace();
+                GUILayoutX horzLayout = vertLayout.AddLayoutX();
+                vertLayout.AddFlexibleSpace();
+
+                horzLayout.AddFlexibleSpace();
+                horzLayout.AddElement(warningLbl);
+                horzLayout.AddFlexibleSpace();
+                
+                return;
+            }
+
+            // TODO - Retrieve Animation & AnimationClip from the selected object, fill curves dictionary
+            //  - If not available, show a button to create new animation clip
+
             lengthField = new GUIFloatField(new LocEdString("Length"), 50);
             fpsField = new GUIIntField(new LocEdString("FPS"), 50);
             yRangeField = new GUIFloatField(new LocEdString("Y range"), 50);
             addKeyframeBtn = new GUIButton(new LocEdString("Add keyframe"));
 
+            addPropertyBtn = new GUIButton(new LocEdString("Add property"));
+            delPropertyBtn = new GUIButton(new LocEdString("Delete selected"));
+
             lengthField.Value = 60.0f;
             fpsField.Value = 1;
             yRangeField.Value = 20.0f;
 
+            addPropertyBtn.OnClick += () =>
+            {
+                Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
+                FieldSelectionWindow fieldSelection = DropDownWindow.Open<FieldSelectionWindow>(this, windowPos);
+                fieldSelection.OnFieldSelected += OnFieldAdded;
+            };
+
+            delPropertyBtn.OnClick += () =>
+            {
+                LocEdString title = new LocEdString("Warning");
+                LocEdString message = new LocEdString("Are you sure you want to remove all selected fields?");
+
+                DialogBox.Open(title, message, DialogBox.Type.YesNo, x =>
+                {
+                    if (x == DialogBox.ResultType.Yes)
+                    {
+                        RemoveSelectedFields();
+                    }
+                });
+            };
+
             lengthField.OnChanged += x =>
             {
                 guiCurveEditor.SetRange(lengthField.Value, yRangeField.Value);
@@ -82,37 +179,172 @@ namespace BansheeEditor
             buttonLayout.AddElement(addKeyframeBtn);
             buttonLayout.AddSpace(5);
 
-            editorPanel = mainLayout.AddPanel();
             buttonLayoutHeight = lengthField.Bounds.height;
 
-            guiCurveEditor = new GUICurveEditor(this, editorPanel, Width, Height - buttonLayoutHeight);
-            guiCurveEditor.SetCurves(CreateDummyCurves());
+            GUILayout contentLayout = mainLayout.AddLayoutX();
+            GUILayout fieldDisplayLayout = contentLayout.AddLayoutY(GUIOption.FixedWidth(FIELD_DISPLAY_WIDTH));
+
+            guiFieldDisplay = new GUIAnimFieldDisplay(fieldDisplayLayout, FIELD_DISPLAY_WIDTH,
+                Height - buttonLayoutHeight * 2, selectedSO);
+            guiFieldDisplay.OnSelectionChanged += OnFieldSelectionChanged;
+
+            GUILayout bottomButtonLayout = fieldDisplayLayout.AddLayoutX();
+            bottomButtonLayout.AddElement(addPropertyBtn);
+            bottomButtonLayout.AddElement(delPropertyBtn);
+
+            GUILayout curveLayout = contentLayout.AddLayoutY();
+
+            editorPanel = curveLayout.AddPanel();
+
+            int curveEditorWidth = Math.Max(0, Width - FIELD_DISPLAY_WIDTH);
+            guiCurveEditor = new GUICurveEditor(this, editorPanel, curveEditorWidth, Height - buttonLayoutHeight);
             guiCurveEditor.Redraw();
+
+            isInitialized = true;
         }
 
-        private EdAnimationCurve[] CreateDummyCurves()
+        private void OnPointerPressed(PointerEvent ev)
         {
-            EdAnimationCurve[] curves = new EdAnimationCurve[1];
-            curves[0] = new EdAnimationCurve();
+            if (!isInitialized)
+                return;
+
+            guiCurveEditor.OnPointerPressed(ev);
+        }
 
-            curves[0].AddKeyframe(0.0f, 1.0f);
-            curves[0].AddKeyframe(10.0f, 5.0f);
-            curves[0].AddKeyframe(15.0f, -2.0f);
-            curves[0].AddKeyframe(20.0f, 3.0f, TangentMode.InStep);
-            curves[0].Apply();
+        private void OnPointerMoved(PointerEvent ev)
+        {
+            if (!isInitialized)
+                return;
 
-            return curves;
+            guiCurveEditor.OnPointerMoved(ev);
         }
 
-        protected override void WindowResized(int width, int height)
+        private void OnPointerReleased(PointerEvent ev)
+        {
+            if (!isInitialized)
+                return;
+
+            guiCurveEditor.OnPointerReleased(ev);
+        }
+
+        private void OnButtonUp(ButtonEvent ev)
+        {
+            if (!isInitialized)
+                return;
+
+            guiCurveEditor.OnButtonUp(ev);
+        }
+
+        private void UpdateDisplayedCurves()
         {
-            guiCurveEditor.SetSize(width, height - buttonLayoutHeight);
+            List<EdAnimationCurve> curvesToDisplay = new List<EdAnimationCurve>();
+            for (int i = 0; i < selectedFields.Count; i++)
+            {
+                EdAnimationCurve curve;
+                if(curves.TryGetValue(selectedFields[i], out curve))
+                    curvesToDisplay.Add(curve);
+            }
+
+            guiCurveEditor.SetCurves(curvesToDisplay.ToArray());
             guiCurveEditor.Redraw();
         }
 
-        private void OnEditorUpdate()
+        private void OnFieldAdded(string path, SerializableProperty.FieldType type)
+        {
+            guiFieldDisplay.AddField(path);
+
+            switch (type)
+            {
+                case SerializableProperty.FieldType.Vector4:
+                {
+                    string[] subPaths = { ".x", ".y", ".z", ".w" };
+
+                    for (int i = 0; i < subPaths.Length; i++)
+                    {
+                        string subFieldPath = path + subPaths[i];
+                        curves[subFieldPath] = new EdAnimationCurve();
+                        selectedFields.Add(subFieldPath);
+                    }
+                }
+                    break;
+                case SerializableProperty.FieldType.Vector3:
+                    {
+                        string[] subPaths = { ".x", ".y", ".z" };
+
+                        for (int i = 0; i < subPaths.Length; i++)
+                        {
+                            string subFieldPath = path + subPaths[i];
+                            curves[subFieldPath] = new EdAnimationCurve();
+                            selectedFields.Add(subFieldPath);
+                        }
+                    }
+                    break;
+                case SerializableProperty.FieldType.Vector2:
+                    {
+                        string[] subPaths = { ".x", ".y" };
+
+                        for (int i = 0; i < subPaths.Length; i++)
+                        {
+                            string subFieldPath = path + subPaths[i];
+                            curves[subFieldPath] = new EdAnimationCurve();
+                            selectedFields.Add(subFieldPath);
+                        }
+                    }
+                    break;
+                case SerializableProperty.FieldType.Color:
+                    {
+                        string[] subPaths = { ".r", ".g", ".b", ".a" };
+
+                        for (int i = 0; i < subPaths.Length; i++)
+                        {
+                            string subFieldPath = path + subPaths[i];
+                            curves[subFieldPath] = new EdAnimationCurve();
+                            selectedFields.Add(subFieldPath);
+                        }
+                    }
+                    break;
+                default: // Primitive type
+                    {
+                        curves[path] = new EdAnimationCurve();
+                        selectedFields.Add(path);
+                    }
+                    break;
+            }
+
+            UpdateDisplayedCurves();
+        }
+
+        private void OnFieldSelectionChanged(string path, bool selected)
+        {
+            if (selected)
+                selectedFields.Add(path);
+            else
+                selectedFields.Remove(path);
+
+            UpdateDisplayedCurves();
+        }
+
+        private void RemoveSelectedFields()
+        {
+            for (int i = 0; i < selectedFields.Count; i++)
+            {
+                selectedFields.Remove(selectedFields[i]);
+                curves.Remove(selectedFields[i]);
+            }
+
+            List<string> existingFields = new List<string>();
+            foreach(var KVP in curves)
+                existingFields.Add(KVP.Key);
+
+            guiFieldDisplay.SetFields(existingFields.ToArray());
+
+            selectedFields.Clear();
+            UpdateDisplayedCurves();
+        }
+
+        private void OnSelectionChanged(SceneObject[] sceneObjects, string[] resourcePaths)
         {
-            
+            Rebuild();
         }
     }
 

+ 2 - 2
Source/SBansheeEngine/Include/BsScriptGUIElement.h

@@ -43,7 +43,7 @@ namespace BansheeEngine
 		void initialize(GUIElementBase* element);
 
 		/** @copydoc ScriptObjectBase::_onManagedInstanceDeleted */
-		virtual void _onManagedInstanceDeleted() override;
+		void _onManagedInstanceDeleted() override;
 
 		/**	Triggered when the focus changes for the underlying GUIElementBase. */
 		static void onFocusChanged(MonoObject* instance, bool focus);
@@ -79,7 +79,7 @@ namespace BansheeEngine
 		virtual ~ScriptGUIElementTBase() {}
 
 		/** @copydoc ScriptGUIElementBaseTBase::destroy */
-		virtual void destroy() override;
+		void destroy() override;
 	};
 
 	/**

+ 0 - 1
Source/SBansheeEngine/Include/BsScriptGUILayout.h

@@ -61,7 +61,6 @@ namespace BansheeEngine
 		GUILayout* mLayout;
 		Vector<ChildInfo> mChildren;
 
-		bool mIsDestroyed;
 		bool mOwnsNative;
 
 	private:

+ 1 - 1
Source/SBansheeEngine/Source/BsScriptGUILayout.cpp

@@ -16,7 +16,7 @@
 namespace BansheeEngine
 {
 	ScriptGUILayout::ScriptGUILayout(MonoObject* instance, GUILayout* layout, bool ownsNative)
-		:TScriptGUIElementBase(instance, layout), mLayout(layout), mIsDestroyed(false), mOwnsNative(ownsNative)
+		:TScriptGUIElementBase(instance, layout), mLayout(layout), mOwnsNative(ownsNative)
 	{ }
 
 	void ScriptGUILayout::initRuntimeData()

+ 7 - 1
Source/SBansheeEngine/Source/BsScriptGUIUtility.cpp

@@ -34,8 +34,14 @@ namespace BansheeEngine
 
 	void ScriptGUILayoutUtility::internal_CalculateBounds(ScriptGUIElementBaseTBase* guiElement, ScriptGUILayout* relativeTo, Rect2I* output)
 	{
+		if (guiElement->isDestroyed())
+		{
+			*output = Rect2I();
+			return;
+		}
+
 		GUIPanel* relativeToPanel = nullptr;
-		if (relativeTo != nullptr)
+		if (relativeTo != nullptr && !relativeTo->isDestroyed())
 			relativeToPanel = static_cast<GUIPanel*>(relativeTo->getGUIElement());
 
 		*output = guiElement->getGUIElement()->getBounds(relativeToPanel);