Browse Source

Refactor: Much improved way of creating custom inspectors
- Custom inspectors now transparently support undo-redo
- Added support for creating conditionally displayed GUI for fields dependent on other fields
- Refactored all physics joint inspectors to use the new approach, removing a lot of boilerplate code

BearishSun 6 years ago
parent
commit
df0775ba6b

+ 15 - 278
Source/EditorManaged/Inspectors/D6JointInspector.cs

@@ -15,298 +15,35 @@ namespace bs.Editor
     [CustomInspector(typeof(D6Joint))]
     internal class D6JointInspector : JointInspector
     {
-        private GUIEnumField[] motionFields = new GUIEnumField[(int) D6JointAxis.Count];
-
-        private GUIToggle linearLimitFoldout = new GUIToggle(new LocEdString("Linear limit"), EditorStyles.Foldout);
-        private LimitLinearGUI limitLinearGUI;
-        private GUIToggle twistLimitFoldout = new GUIToggle(new LocEdString("Twist limit"), EditorStyles.Foldout);
-        private LimitAngularRangeGUI limitTwistGUI;
-        private GUIToggle swingLimitFoldout = new GUIToggle(new LocEdString("Swing limit"), EditorStyles.Foldout);
-        private LimitConeRangeGUI limitSwingGUI;
-
-        private GUIToggle driveFoldout = new GUIToggle(new LocEdString("Drive"), EditorStyles.Foldout);
-        private D6JointDriveGUI[] drivesGUI = new D6JointDriveGUI[(int)D6JointDriveType.Count];
-        private GUIVector3Field drivePositionField = new GUIVector3Field(new LocEdString("Drive position"));
-        private GUIVector3Field driveRotationField = new GUIVector3Field(new LocEdString("Drive rotation"));
-        private GUIVector3Field driveLinVelocityField = new GUIVector3Field(new LocEdString("Drive linear velocity"));
-        private GUIVector3Field driveAngVelocityField = new GUIVector3Field(new LocEdString("Drive angular velocity"));
-
-        private GUILayoutX linearLimitLayout;
-        private GUILayoutX twistLimitLayout;
-        private GUILayoutX swingLimitLayout;
-        private GUILayoutX driveLayout;
-
         /// <inheritdoc/>
         protected internal override void Initialize()
         {
-            D6Joint joint = InspectedObject as D6Joint;
-
-            if (joint != null)
-                BuildGUI(joint);
-        }
-
-        /// <inheritdoc/>
-        protected internal override InspectableState Refresh()
-        {
-            D6Joint joint = InspectedObject as D6Joint;
-            if (joint == null)
-                return InspectableState.NotModified;
-
-            Refresh(joint);
-
-            InspectableState oldState = modifyState;
-            if (modifyState.HasFlag(InspectableState.Modified))
-                modifyState = InspectableState.NotModified;
-
-            return oldState;
-        }
-
-        /// <summary>
-        /// Creates GUI elements for fields specific to the spherical joint.
-        /// </summary>
-        protected void BuildGUI(D6Joint joint)
-        {
-            for (int i = 0; i < (int) D6JointAxis.Count; i++)
-            {
-                D6JointAxis axis = (D6JointAxis) i;
-                string entryName = Enum.GetName(typeof (D6JointAxis), axis);
-
-                motionFields[i] = new GUIEnumField(typeof (D6JointMotion), new LocEdString(entryName));
-                motionFields[i].OnSelectionChanged += x =>
-                {
-                    joint.SetMotion(axis, (D6JointMotion)x);
-
-                    MarkAsModified();
-                    ConfirmModify();
-                };
-            }
-
-            linearLimitFoldout.AcceptsKeyFocus = false;
-            linearLimitFoldout.OnToggled += x =>
-            {
-                linearLimitLayout.Active = x;
-                Persistent.SetBool("linearLimit_Expanded", x);
-            };
-
-            twistLimitFoldout.AcceptsKeyFocus = false;
-            twistLimitFoldout.OnToggled += x =>
-            {
-                twistLimitLayout.Active = x;
-                Persistent.SetBool("twistLimit_Expanded", x);
-            };
-
-            swingLimitFoldout.AcceptsKeyFocus = false;
-            swingLimitFoldout.OnToggled += x =>
-            {
-                swingLimitLayout.Active = x;
-                Persistent.SetBool("swingLimit_Expanded", x);
-            };
-
-            driveFoldout.AcceptsKeyFocus = false;
-            driveFoldout.OnToggled += x =>
-            {
-                driveLayout.Active = x;
-                Persistent.SetBool("drive_Expanded", x);
-            };
-
-            drivePositionField.OnValueChanged += x => { joint.SetDriveTransform(x, joint.DriveRotation); MarkAsModified(); };
-            drivePositionField.OnFocusLost += ConfirmModify;
-            drivePositionField.OnConfirm += x => ConfirmModify();
-
-            driveRotationField.OnValueChanged += x => { joint.SetDriveTransform(joint.DrivePosition, Quaternion.FromEuler(x)); MarkAsModified(); };
-            driveRotationField.OnFocusLost += ConfirmModify;
-            driveRotationField.OnConfirm += x => ConfirmModify();
-
-            driveLinVelocityField.OnValueChanged += x => { joint.SetDriveVelocity(x, joint.DriveAngularVelocity); MarkAsModified(); };
-            driveLinVelocityField.OnFocusLost += ConfirmModify;
-            driveLinVelocityField.OnConfirm += x => ConfirmModify();
-
-            driveAngVelocityField.OnValueChanged += x => { joint.SetDriveVelocity(joint.DriveLinearVelocity, x); MarkAsModified(); };
-            driveAngVelocityField.OnFocusLost += ConfirmModify;
-            driveAngVelocityField.OnConfirm += x => ConfirmModify();
-
-            for (int i = 0; i < (int) D6JointAxis.Count; i++)
-                Layout.AddElement(motionFields[i]);
-
-            Layout.AddElement(linearLimitFoldout);
-            linearLimitLayout = Layout.AddLayoutX();
-            {
-                linearLimitLayout.AddSpace(10);
-                GUILayoutY linearLimitContentsLayout = linearLimitLayout.AddLayoutY();
-                limitLinearGUI = new LimitLinearGUI(joint.LimitLinear, linearLimitContentsLayout, Persistent);
-                limitLinearGUI.OnChanged += (x, y) =>
-                {
-                    joint.LimitLinear = x;
-                    joint.LimitLinear.SetBase(y);
-
-                    MarkAsModified();
-                };
-                limitLinearGUI.OnConfirmed += ConfirmModify;
-            }
-
-            Layout.AddElement(twistLimitFoldout);
-            twistLimitLayout = Layout.AddLayoutX();
-            {
-                twistLimitLayout.AddSpace(10);
-                GUILayoutY twistLimitContentsLayout = twistLimitLayout.AddLayoutY();
-                limitTwistGUI = new LimitAngularRangeGUI(joint.LimitTwist, twistLimitContentsLayout, Persistent);
-                limitTwistGUI.OnChanged += (x, y) =>
-                {
-                    joint.LimitTwist = x;
-                    joint.LimitTwist.SetBase(y);
+            D6Joint joint = (D6Joint) InspectedObject;
+            BuildGUI(joint, true);
 
-                    MarkAsModified();
-                };
-                limitTwistGUI.OnConfirmed += ConfirmModify;
-            }
-
-            Layout.AddElement(swingLimitFoldout);
-            swingLimitLayout = Layout.AddLayoutX();
-            {
-                swingLimitLayout.AddSpace(10);
-                GUILayoutY swingLimitContentsLayout = swingLimitLayout.AddLayoutY();
-                limitSwingGUI = new LimitConeRangeGUI(joint.LimitSwing, swingLimitContentsLayout, Persistent);
-                limitSwingGUI.OnChanged += (x, y) =>
-                {
-                    joint.LimitSwing = x;
-                    joint.LimitSwing.SetBase(y);
-
-                    MarkAsModified();
-                };
-                limitSwingGUI.OnConfirmed += ConfirmModify;
-            }
+            drawer.AddDefault(joint, typeof(D6Joint));
 
-            Layout.AddElement(driveFoldout);
-            driveLayout = Layout.AddLayoutX();
+            for (int i = 0; i < (int)D6JointAxis.Count; i++)
             {
-                driveLayout.AddSpace(10);
-                GUILayoutY driveContentsLayout = driveLayout.AddLayoutY();
+                D6JointAxis axis = (D6JointAxis)i;
+                string entryName = Enum.GetName(typeof(D6JointAxis), axis);
 
-                for (int i = 0; i < (int) D6JointDriveType.Count; i++)
-                {
-                    D6JointDriveType type = (D6JointDriveType)i;
-
-                    drivesGUI[i] = new D6JointDriveGUI(joint.GetDrive(type), driveContentsLayout);
-                    drivesGUI[i].OnChanged += x => { joint.SetDrive(type, x); MarkAsModified(); };
-                    drivesGUI[i].OnConfirmed += ConfirmModify;
-                }
-
-                driveContentsLayout.AddElement(drivePositionField);
-                driveContentsLayout.AddElement(driveRotationField);
-                driveContentsLayout.AddElement(driveLinVelocityField);
-                driveContentsLayout.AddElement(driveAngVelocityField);
+                drawer.AddField(entryName, () => joint.GetMotion(axis), x => joint.SetMotion(axis, x));
             }
 
-            linearLimitLayout.Active = Persistent.GetBool("linearLimit_Expanded");
-            twistLimitLayout.Active = Persistent.GetBool("twistLimit_Expanded");
-            swingLimitLayout.Active = Persistent.GetBool("swingLimit_Expanded");
-            driveLayout.Active = Persistent.GetBool("drive_Expanded");
-
-            base.BuildGUI(joint, true);
-        }
-
-        /// <summary>
-        /// Updates all GUI elements from current values in the joint.
-        /// </summary>
-        /// <param name="joint">Joint to update the GUI from.</param>
-        protected void Refresh(D6Joint joint)
-        {
-            for (int i = 0; i < (int) D6JointAxis.Count; i++)
-                motionFields[i].Value = (ulong)joint.GetMotion((D6JointAxis) i);
-
-            limitLinearGUI.Limit = joint.LimitLinear;
-            limitTwistGUI.Limit = joint.LimitTwist;
-            limitSwingGUI.Limit = joint.LimitSwing;
-
-            for (int i = 0; i < (int) D6JointDriveType.Count; i++)
-                drivesGUI[i].Drive = joint.GetDrive((D6JointDriveType) i);
-
-            drivePositionField.Value = joint.DrivePosition;
-            driveRotationField.Value = joint.DriveRotation.ToEuler();
-            driveLinVelocityField.Value = joint.DriveLinearVelocity;
-            driveAngVelocityField.Value = joint.DriveAngularVelocity;
-
-            base.Refresh(joint);
-        }
-    }
-
-    /// <summary>
-    /// Draws GUI elements for inspecting an <see cref="D6JointDrive"/> object.
-    /// </summary>
-    internal class D6JointDriveGUI
-    {
-        private D6JointDrive driveData;
-
-        private GUIFloatField stiffnessField = new GUIFloatField(new LocEdString("Stiffness"));
-        private GUIFloatField dampingField = new GUIFloatField(new LocEdString("Damping"));
-        private GUIFloatField forceLimitField = new GUIFloatField(new LocEdString("Force limit"));
-        private GUIToggleField accelerationField = new GUIToggleField(new LocEdString("Acceleration"));
+            drawer.AddField("Drive position", () => joint.DrivePosition, x => joint.SetDriveTransform(x, joint.DriveRotation));
+            drawer.AddField("Drive rotation", () => joint.DriveRotation.ToEuler(), x => joint.SetDriveTransform(joint.DrivePosition, Quaternion.FromEuler(x)));
+            drawer.AddField("Drive linear velocity", () => joint.DriveLinearVelocity, x => joint.SetDriveVelocity(x, joint.DriveAngularVelocity));
+            drawer.AddField("Drive angular velocity", () => joint.DriveAngularVelocity, x => joint.SetDriveVelocity(joint.DriveLinearVelocity, x));
 
-        public Action<D6JointDrive> OnChanged;
-        public Action OnConfirmed;
-
-        /// <summary>
-        /// Current drive properties.
-        /// </summary>
-        public D6JointDrive Drive
-        {
-            set
+            for (int i = 0; i < (int)D6JointDriveType.Count; i++)
             {
-                driveData = value;
+                D6JointDriveType type = (D6JointDriveType)i;
+                string entryName = Enum.GetName(typeof(D6JointDriveType), type);
 
-                stiffnessField.Value = driveData.stiffness;
-                dampingField.Value = driveData.damping;
-                forceLimitField.Value = driveData.forceLimit;
-                accelerationField.Value = driveData.acceleration;
+                drawer.AddField(entryName, () => joint.GetDrive(type), x => joint.SetDrive(type, x));
             }
         }
-
-        /// <summary>
-        /// Constructs a new set of GUI elements for inspecting the drive object.
-        /// </summary>
-        /// <param name="drive">Initial values to assign to the GUI elements.</param>
-        /// <param name="layout">Layout to append the GUI elements to.</param>
-        public D6JointDriveGUI(D6JointDrive drive, GUILayout layout)
-        {
-            driveData = drive;
-
-            stiffnessField.OnChanged += x => { driveData.stiffness = x; MarkAsModified(); };
-            stiffnessField.OnFocusLost += ConfirmModify;
-            stiffnessField.OnConfirmed += ConfirmModify;
-
-            dampingField.OnChanged += x => { driveData.damping = x; MarkAsModified(); };
-            dampingField.OnFocusLost += ConfirmModify;
-            dampingField.OnConfirmed += ConfirmModify;
-
-            forceLimitField.OnChanged += x => { driveData.forceLimit = x; MarkAsModified(); };
-            forceLimitField.OnFocusLost += ConfirmModify;
-            forceLimitField.OnConfirmed += ConfirmModify;
-
-            accelerationField.OnChanged += x => { driveData.acceleration = x; MarkAsModified(); ConfirmModify(); };
-
-            layout.AddElement(stiffnessField);
-            layout.AddElement(dampingField);
-            layout.AddElement(forceLimitField);
-            layout.AddElement(accelerationField);
-        }
-
-        /// <summary>
-        /// Marks the contents of the inspector as modified.
-        /// </summary>
-        private void MarkAsModified()
-        {
-            if (OnChanged != null)
-                OnChanged(driveData);
-        }
-
-        /// <summary>
-        /// Confirms any queued modifications.
-        /// </summary>
-        private void ConfirmModify()
-        {
-            if (OnConfirmed != null)
-                OnConfirmed();
-        }
     }
 
     /** @} */

+ 15 - 138
Source/EditorManaged/Inspectors/DistanceJointInspector.cs

@@ -14,151 +14,28 @@ namespace bs.Editor
     [CustomInspector(typeof(DistanceJoint))]
     internal class DistanceJointInspector : JointInspector
     {
-        private GUIToggleField enableMinLimitField = new GUIToggleField(new LocEdString("Enable minimum limit"));
-        private GUIFloatField minLimitField = new GUIFloatField(new LocEdString("Minimum distance"));
-        private GUIToggleField enableMaxLimitField = new GUIToggleField(new LocEdString("Enable maximum limit"));
-        private GUIFloatField maxLimitField = new GUIFloatField(new LocEdString("Maximum distance"));
-        private GUIFloatField toleranceField = new GUIFloatField(new LocEdString("Tolerance"));
-        private GUIToggleField enableSpringField = new GUIToggleField(new LocEdString("Enable spring"));
-        private SpringGUI springGUI;
-
-        private GUILayoutX springLayout;
-
         /// <inheritdoc/>
         protected internal override void Initialize()
         {
-            DistanceJoint joint = InspectedObject as DistanceJoint;
-
-            if (joint != null)
-                BuildGUI(joint);
-        }
-
-        /// <inheritdoc/>
-        protected internal override InspectableState Refresh()
-        {
-            DistanceJoint joint = InspectedObject as DistanceJoint;
-            if (joint == null)
-                return InspectableState.NotModified;
-
-            Refresh(joint);
-
-            InspectableState oldState = modifyState;
-            if (modifyState.HasFlag(InspectableState.Modified))
-                modifyState = InspectableState.NotModified;
-
-            return oldState;
-        }
-
-        /// <summary>
-        /// Creates GUI elements for fields specific to the distance joint.
-        /// </summary>
-        protected void BuildGUI(DistanceJoint joint)
-        {
-            enableMinLimitField.OnChanged += x =>
-            {
-                joint.SetFlag(DistanceJointFlag.MinDistance, x);
-                MarkAsModified();
-                ConfirmModify();
-
-                minLimitField.Active = x;
-            };
-
-            minLimitField.OnChanged += x => { joint.MinDistance = x; MarkAsModified(); };
-            minLimitField.OnFocusLost += ConfirmModify;
-            minLimitField.OnConfirmed += ConfirmModify;
-
-            enableMaxLimitField.OnChanged += x =>
-            {
-                joint.SetFlag(DistanceJointFlag.MaxDistance, x);
-                MarkAsModified();
-                ConfirmModify();
-
-                maxLimitField.Active = x;
-            };
-
-            maxLimitField.OnChanged += x => { joint.MaxDistance = x; MarkAsModified(); };
-            maxLimitField.OnFocusLost += ConfirmModify;
-            maxLimitField.OnConfirmed += ConfirmModify;
-
-            toleranceField.OnChanged += x => { joint.Tolerance = x; MarkAsModified(); };
-            toleranceField.OnFocusLost += ConfirmModify;
-            toleranceField.OnConfirmed += ConfirmModify;
-
-            enableSpringField.OnChanged += x =>
-            {
-                joint.SetFlag(DistanceJointFlag.Spring, x);
-                MarkAsModified();
-                ConfirmModify();
-
-                springLayout.Active = x;
-            };
-
-            Layout.AddElement(enableMinLimitField);
-            GUILayoutX minLimitLayout = Layout.AddLayoutX();
-            {
-                minLimitLayout.AddSpace(10);
-                minLimitLayout.AddElement(minLimitField);
-            }
-
-            Layout.AddElement(enableMaxLimitField);
-            GUILayoutX maxLimitLayout = Layout.AddLayoutX();
-            {
-                maxLimitLayout.AddSpace(10);
-                maxLimitLayout.AddElement(maxLimitField);
-            }
-
-            Layout.AddElement(toleranceField);
-            Layout.AddElement(enableSpringField);
-            springLayout = Layout.AddLayoutX();
-            {
-                springLayout.AddSpace(10);
-                springGUI = new SpringGUI(joint.Spring, springLayout);
-                springGUI.OnChanged += x => { joint.Spring = x; MarkAsModified(); };
-                springGUI.OnConfirmed += ConfirmModify;
-            }
-
-            minLimitField.Active = joint.HasFlag(DistanceJointFlag.MinDistance);
-            maxLimitField.Active = joint.HasFlag(DistanceJointFlag.MaxDistance);
-            springLayout.Active = joint.HasFlag(DistanceJointFlag.Spring);
-
-            base.BuildGUI(joint, true);
-        }
-
-        /// <summary>
-        /// Updates all GUI elements from current values in the joint.
-        /// </summary>
-        /// <param name="joint">Joint to update the GUI from.</param>
-        protected void Refresh(DistanceJoint joint)
-        {
-            bool enableMinDistanceLimit = joint.HasFlag(DistanceJointFlag.MinDistance);
-            if (enableMinLimitField.Value != enableMinDistanceLimit)
-            {
-                enableMinLimitField.Value = enableMinDistanceLimit;
-                minLimitField.Active = enableMinDistanceLimit;
-            }
-
-            minLimitField.Value = joint.MinDistance;
-
-            bool enableMaxDistanceLimit = joint.HasFlag(DistanceJointFlag.MaxDistance);
-            if (enableMaxLimitField.Value != enableMaxDistanceLimit)
-            {
-                enableMaxLimitField.Value = enableMaxDistanceLimit;
-                maxLimitField.Active = enableMaxDistanceLimit;
-            }
+            DistanceJoint joint = (DistanceJoint) InspectedObject;
+            BuildGUI(joint, true);
 
-            maxLimitField.Value = joint.MaxDistance;
-            toleranceField.Value = joint.Tolerance;
+            drawer.AddDefault(joint, typeof(DistanceJoint));
 
-            bool enableSpring = joint.HasFlag(DistanceJointFlag.Spring);
-            if (enableSpringField.Value != enableSpring)
-            {
-                enableSpringField.Value = enableSpring;
-                springLayout.Active = enableSpring;
-            }
+            drawer.AddField("Enable minimum limit",
+                () => joint.HasFlag(DistanceJointFlag.MinDistance),
+                x => joint.SetFlag(DistanceJointFlag.MinDistance, x));
+            drawer.AddConditional("MinDistance", () => joint.HasFlag(DistanceJointFlag.MinDistance));
 
-            springGUI.Spring = joint.Spring;
+            drawer.AddField("Enable maximum limit",
+                () => joint.HasFlag(DistanceJointFlag.MaxDistance),
+                x => joint.SetFlag(DistanceJointFlag.MaxDistance, x));
+            drawer.AddConditional("MaxDistance", () => joint.HasFlag(DistanceJointFlag.MaxDistance));
 
-            base.Refresh(joint);
+            drawer.AddField("Enable spring",
+                () => joint.HasFlag(DistanceJointFlag.Spring),
+                x => joint.SetFlag(DistanceJointFlag.Spring, x));
+            drawer.AddConditional("Spring", () => joint.HasFlag(DistanceJointFlag.Spring));
         }
     }
 

+ 2 - 20
Source/EditorManaged/Inspectors/FixedJointInspector.cs

@@ -17,26 +17,8 @@ namespace bs.Editor
         /// <inheritdoc/>
         protected internal override void Initialize()
         {
-            FixedJoint joint = InspectedObject as FixedJoint;
-
-            if (joint != null)
-                BuildGUI(joint, false);
-        }
-
-        /// <inheritdoc/>
-        protected internal override InspectableState Refresh()
-        {
-            FixedJoint joint = InspectedObject as FixedJoint;
-            if (joint == null)
-                return InspectableState.NotModified;
-
-            Refresh(joint);
-
-            InspectableState oldState = modifyState;
-            if (modifyState.HasFlag(InspectableState.Modified))
-                modifyState = InspectableState.NotModified;
-
-            return oldState;
+            FixedJoint joint = (FixedJoint) InspectedObject;
+            BuildGUI(joint, false);
         }
     }
 

+ 11 - 178
Source/EditorManaged/Inspectors/HingeJointInspector.cs

@@ -14,190 +14,23 @@ namespace bs.Editor
     [CustomInspector(typeof(HingeJoint))]
     internal class HingeJointInspector : JointInspector
     {
-        private GUIToggleField enableLimitField = new GUIToggleField(new LocEdString("Enable limit"));
-        private LimitAngularRangeGUI limitGUI;
-
-        private GUIToggleField enableDriveField = new GUIToggleField(new LocEdString("Enable drive"));
-        private GUIFloatField speedField = new GUIFloatField(new LocEdString("Speed"));
-        private GUIFloatField forceLimitField = new GUIFloatField(new LocEdString("Force limit"));
-        private GUIFloatField gearRatioField = new GUIFloatField(new LocEdString("Gear ratio"));
-        private GUIToggleField freeSpinField = new GUIToggleField(new LocEdString("Free spin"));
-
-        private GUILayoutX limitLayout;
-        private GUILayoutX driveLayout;
-
         /// <inheritdoc/>
         protected internal override void Initialize()
         {
-            HingeJoint joint = InspectedObject as HingeJoint;
-
-            if (joint != null)
-                BuildGUI(joint);
-        }
-
-        /// <inheritdoc/>
-        protected internal override InspectableState Refresh()
-        {
-            HingeJoint joint = InspectedObject as HingeJoint;
-            if (joint == null)
-                return InspectableState.NotModified;
-
-            Refresh(joint);
-
-            InspectableState oldState = modifyState;
-            if (modifyState.HasFlag(InspectableState.Modified))
-                modifyState = InspectableState.NotModified;
-
-            return oldState;
-        }
-
-        /// <summary>
-        /// Creates GUI elements for fields specific to the hinge joint.
-        /// </summary>
-        protected void BuildGUI(HingeJoint joint)
-        {
-            enableLimitField.OnChanged += x =>
-            {
-                joint.SetFlag(HingeJointFlag.Limit, x);
-                MarkAsModified();
-                ConfirmModify();
-
-                ToggleLimitFields(x);
-            };
-            
-            enableDriveField.OnChanged += x =>
-            {
-                joint.SetFlag(HingeJointFlag.Drive, x);
-                MarkAsModified();
-                ConfirmModify();
-
-                ToggleDriveFields(x);
-            };
-
-            speedField.OnChanged += x =>
-            {
-                HingeJointDrive driveData = joint.Drive;
-                driveData.speed = x;
-                joint.Drive = driveData;
-
-                MarkAsModified();
-            };
-            speedField.OnFocusLost += ConfirmModify;
-            speedField.OnConfirmed += ConfirmModify;
-
-            forceLimitField.OnChanged += x =>
-            {
-                HingeJointDrive driveData = joint.Drive;
-                driveData.forceLimit = x;
-                joint.Drive = driveData;
-
-                MarkAsModified();
-            };
-            forceLimitField.OnFocusLost += ConfirmModify;
-            forceLimitField.OnConfirmed += ConfirmModify;
-
-            gearRatioField.OnChanged += x =>
-            {
-                HingeJointDrive driveData = joint.Drive;
-                driveData.gearRatio = x;
-                joint.Drive = driveData;
+            HingeJoint joint = (HingeJoint)InspectedObject;
+            BuildGUI(joint, true);
 
-                MarkAsModified();
-            };
-            gearRatioField.OnFocusLost += ConfirmModify;
-            gearRatioField.OnConfirmed += ConfirmModify;
+            drawer.AddDefault(joint, typeof(HingeJoint));
 
-            freeSpinField.OnChanged += x =>
-            {
-                HingeJointDrive driveData = joint.Drive;
-                driveData.freeSpin = x;
-                joint.Drive = driveData;
+            drawer.AddField("Enable limit",
+                () => joint.HasFlag(HingeJointFlag.Limit),
+                x => joint.SetFlag(HingeJointFlag.Limit, x));
+            drawer.AddConditional("Limit", () => joint.HasFlag(HingeJointFlag.Limit));
 
-                MarkAsModified();
-                ConfirmModify();
-            };
-
-            Layout.AddElement(enableLimitField);
-            limitLayout = Layout.AddLayoutX();
-            {
-                limitLayout.AddSpace(10);
-
-                GUILayoutY limitContentsLayout = limitLayout.AddLayoutY();
-                limitGUI = new LimitAngularRangeGUI(joint.Limit, limitContentsLayout, Persistent);
-                limitGUI.OnChanged += (x, y) =>
-                {
-                    joint.Limit = x;
-                    joint.Limit.SetBase(y);
-
-                    MarkAsModified();
-                };
-                limitGUI.OnConfirmed += ConfirmModify;
-            }
-
-            Layout.AddElement(enableDriveField);
-            driveLayout = Layout.AddLayoutX();
-            {
-                driveLayout.AddSpace(10);
-
-                GUILayoutY driveContentsLayout = driveLayout.AddLayoutY();
-                driveContentsLayout.AddElement(speedField);
-                driveContentsLayout.AddElement(forceLimitField);
-                driveContentsLayout.AddElement(gearRatioField);
-                driveContentsLayout.AddElement(freeSpinField);
-            }
-
-            ToggleLimitFields(joint.HasFlag(HingeJointFlag.Limit));
-            ToggleDriveFields(joint.HasFlag(HingeJointFlag.Drive));
-
-            base.BuildGUI(joint, true);
-        }
-
-        /// <summary>
-        /// Updates all GUI elements from current values in the joint.
-        /// </summary>
-        /// <param name="joint">Joint to update the GUI from.</param>
-        protected void Refresh(HingeJoint joint)
-        {
-            bool enableLimit = joint.HasFlag(HingeJointFlag.Limit);
-            if (enableLimitField.Value != enableLimit)
-            {
-                enableLimitField.Value = enableLimit;
-                ToggleLimitFields(enableLimit);
-            }
-
-            limitGUI.Limit = joint.Limit;
-
-            bool enableDrive = joint.HasFlag(HingeJointFlag.Drive);
-            if (enableDriveField.Value != enableDrive)
-            {
-                enableDriveField.Value = enableDrive;
-                ToggleDriveFields(enableDrive);
-            }
-
-            speedField.Value = joint.Drive.speed;
-            forceLimitField.Value = joint.Drive.forceLimit;
-            gearRatioField.Value = joint.Drive.gearRatio;
-            freeSpinField.Value = joint.Drive.freeSpin;
-
-            base.Refresh(joint);
-        }
-
-        /// <summary>
-        /// Hides or shows limit property GUI elements.
-        /// </summary>
-        /// <param name="enable">True to show, false to hide.</param>
-        private void ToggleLimitFields(bool enable)
-        {
-            limitLayout.Active = enable;
-        }
-
-        /// <summary>
-        /// Hides or shows drive property GUI elements.
-        /// </summary>
-        /// <param name="enable">True to show, false to hide.</param>
-        private void ToggleDriveFields(bool enable)
-        {
-            driveLayout.Active = enable;
+            drawer.AddField("Enable drive",
+                () => joint.HasFlag(HingeJointFlag.Drive),
+                x => joint.SetFlag(HingeJointFlag.Drive, x));
+            drawer.AddConditional("Drive", () => joint.HasFlag(HingeJointFlag.Drive));
         }
     }
 

+ 9 - 101
Source/EditorManaged/Inspectors/JointInspector.cs

@@ -13,117 +13,25 @@ namespace bs.Editor
     /// </summary>
     internal abstract class JointInspector : Inspector
     {
-        private GUIGameObjectField targetField;
-        private GUIVector3Field targetOffsetField;
-
-        private GUIGameObjectField anchorField;
-        private GUIVector3Field anchorOffsetField;
-
-        private GUIFloatField breakForceField;
-        private GUIFloatField breakTorqueField;
-        private GUIToggleField collisionField;
-
-        private bool showOffsets;
-        protected InspectableState modifyState;
-
-        /// <summary>
-        /// Updates all GUI elements from current values in the joint.
-        /// </summary>
-        /// <param name="joint">Joint to update the GUI from.</param>
-        protected virtual void Refresh(Joint joint)
-        {
-            targetField.Value = joint.GetBody(JointBody.Target);
-            anchorField.Value = joint.GetBody(JointBody.Anchor);
-
-            if (showOffsets)
-            {
-                targetOffsetField.Value = joint.GetPosition(JointBody.Target);
-                anchorOffsetField.Value = joint.GetPosition(JointBody.Anchor);
-            }
-
-            breakForceField.Value = joint.BreakForce;
-            breakTorqueField.Value = joint.BreakTorque;
-            collisionField.Value = joint.EnableCollision;
-        }
-
         /// <summary>
         /// Creates GUI elements for fields common to all joints.
         /// </summary>
         protected virtual void BuildGUI(Joint joint, bool showOffsets)
         {
-            this.showOffsets = showOffsets;
+            drawer.AddField("Target", () => joint.GetBody(JointBody.Target), x => joint.SetBody(JointBody.Target, x));
+            drawer.AddField("Anchor", () => joint.GetBody(JointBody.Anchor), x => joint.SetBody(JointBody.Anchor, x));
 
-            targetField = new GUIGameObjectField(typeof(Rigidbody), new LocEdString("Target"));
-            anchorField = new GUIGameObjectField(typeof(Rigidbody), new LocEdString("Anchor"));
-            
             if (showOffsets)
             {
-                targetOffsetField = new GUIVector3Field(new LocEdString("Target offset"));
-                anchorOffsetField = new GUIVector3Field(new LocEdString("Anchor offset"));
+                drawer.AddField("Target offset", 
+                    () => joint.GetPosition(JointBody.Target), 
+                    x => joint.SetTransform(JointBody.Target, x, joint.GetRotation(JointBody.Target)));
+                drawer.AddField("Anchor offset", 
+                    () => joint.GetPosition(JointBody.Anchor), 
+                    x => joint.SetTransform(JointBody.Anchor, x, joint.GetRotation(JointBody.Anchor)));
             }
 
-            breakForceField = new GUIFloatField(new LocEdString("Break force"));
-            breakTorqueField = new GUIFloatField(new LocEdString("Break torque"));
-            collisionField = new GUIToggleField(new LocEdString("Enable collision"));
-
-            targetField.OnChanged += x => { joint.SetBody(JointBody.Target, (Rigidbody)x); MarkAsModified(); ConfirmModify(); };
-            anchorField.OnChanged += x => { joint.SetBody(JointBody.Anchor, (Rigidbody)x); MarkAsModified(); ConfirmModify(); };
-
-            if(showOffsets)
-            { 
-                targetOffsetField.OnValueChanged += x =>
-                {
-                    joint.SetTransform(JointBody.Target, x, joint.GetRotation(JointBody.Target));
-                    MarkAsModified();
-                };
-                targetOffsetField.OnFocusLost += ConfirmModify;
-                targetOffsetField.OnConfirm += x => ConfirmModify();
-
-                anchorOffsetField.OnValueChanged += x =>
-                {
-                    joint.SetTransform(JointBody.Anchor, x, joint.GetRotation(JointBody.Anchor));
-                    MarkAsModified();
-                };
-                anchorOffsetField.OnFocusLost += ConfirmModify;
-                anchorOffsetField.OnConfirm += x => ConfirmModify();
-            }
-
-            breakForceField.OnChanged += x => { joint.BreakForce = x; MarkAsModified(); };
-            breakForceField.OnFocusLost += ConfirmModify;
-            breakForceField.OnConfirmed += ConfirmModify;
-
-            breakTorqueField.OnChanged += x => { joint.BreakTorque = x; MarkAsModified(); };
-            breakTorqueField.OnFocusLost += ConfirmModify;
-            breakTorqueField.OnConfirmed += ConfirmModify;
-
-            collisionField.OnChanged += x => { joint.EnableCollision = x; MarkAsModified(); ConfirmModify(); };
-            
-            Layout.AddElement(targetField);
-            if(showOffsets)
-                Layout.AddElement(targetOffsetField);
-            Layout.AddElement(anchorField);
-            if(showOffsets)
-                Layout.AddElement(anchorOffsetField);
-            Layout.AddElement(breakForceField);
-            Layout.AddElement(breakTorqueField);
-            Layout.AddElement(collisionField);
-        }
-
-        /// <summary>
-        /// Marks the contents of the inspector as modified.
-        /// </summary>
-        protected void MarkAsModified()
-        {
-            modifyState |= InspectableState.ModifyInProgress;
-        }
-
-        /// <summary>
-        /// Confirms any queued modifications.
-        /// </summary>
-        protected void ConfirmModify()
-        {
-            if (modifyState.HasFlag(InspectableState.ModifyInProgress))
-                modifyState |= InspectableState.Modified;
+            drawer.AddDefault(joint, typeof(Joint));
         }
     }
 

+ 0 - 501
Source/EditorManaged/Inspectors/LimitInspectors.cs

@@ -1,501 +0,0 @@
-//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
-//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-using System;
-using bs;
-
-namespace bs.Editor
-{
-    /** @addtogroup Inspectors
-     *  @{
-     */
-
-    /// <summary>
-    /// Draws GUI elements for inspecting an <see cref="Spring"/> object.
-    /// </summary>
-    internal class SpringGUI
-    {
-        private Spring spring;
-
-        private GUIFloatField stiffnessField = new GUIFloatField(new LocEdString("Stiffness"));
-        private GUIFloatField dampingField = new GUIFloatField(new LocEdString("Damping"));
-
-        public Action<Spring> OnChanged;
-        public Action OnConfirmed;
-
-        /// <summary>
-        /// Current value of the spring object.
-        /// </summary>
-        public Spring Spring
-        {
-            get { return spring; }
-            set
-            {
-                spring = value;
-
-                stiffnessField.Value = value.stiffness;
-                dampingField.Value = value.damping;
-            }
-        }
-
-        /// <summary>
-        /// Constructs a new set of GUI elements for inspecting the spring object.
-        /// </summary>
-        /// <param name="spring">Initial values to assign to the GUI elements.</param>
-        /// <param name="layout">Layout to append the GUI elements to.</param>
-        public SpringGUI(Spring spring, GUILayout layout)
-        {
-            this.spring = spring;
-
-            stiffnessField.OnChanged += x => { this.spring.stiffness = x; MarkAsModified(); };
-            stiffnessField.OnFocusLost += ConfirmModify;
-            stiffnessField.OnConfirmed += ConfirmModify;
-
-            dampingField.OnChanged += x => { this.spring.damping = x; MarkAsModified(); };
-            dampingField.OnFocusLost += ConfirmModify;
-            dampingField.OnConfirmed += ConfirmModify;
-
-            layout.AddElement(stiffnessField);
-            layout.AddElement(dampingField);
-        }
-
-        /// <summary>
-        /// Marks the contents of the inspector as modified.
-        /// </summary>
-        private void MarkAsModified()
-        {
-            if (OnChanged != null)
-                OnChanged(spring);
-        }
-
-        /// <summary>
-        /// Confirms any queued modifications.
-        /// </summary>
-        private void ConfirmModify()
-        {
-            if (OnConfirmed != null)
-                OnConfirmed();
-        }
-    }
-
-    /// <summary>
-    /// Draws GUI elements for inspecting an <see cref="LimitCommon"/> object.
-    /// </summary>
-    internal class LimitCommonGUI
-    {
-        private LimitCommon limitData;
-        private SerializableProperties properties;
-        private string prefix;
-
-        private GUIToggle hardFoldout = new GUIToggle(new LocEdString("Hard"), EditorStyles.Foldout);
-        private GUIFloatField contactDistanceField = new GUIFloatField(new LocEdString("Contact distance"));
-        private GUIToggle softFoldout = new GUIToggle(new LocEdString("Soft"), EditorStyles.Foldout);
-        private GUISliderField restitutionField = new GUISliderField(0, 1, new LocEdString("Restitution"));
-        private GUIToggle springFoldout = new GUIToggle(new LocEdString("Spring"), EditorStyles.Foldout);
-        private SpringGUI springGUI;
-
-        private GUILayoutX hardLimitLayout;
-        private GUILayoutX softLimitLayout;
-        private GUILayoutX springLayout;
-
-        public Action<LimitCommon> OnChanged;
-        public Action OnConfirmed;
-
-        /// <summary>
-        /// Current limit properties.
-        /// </summary>
-        public LimitCommon LimitData
-        {
-            get { return limitData; }
-            set
-            {
-                limitData = value;
-
-                contactDistanceField.Value = value.contactDist;
-                restitutionField.Value = value.restitution;
-                springGUI.Spring = value.spring;
-            }
-        }
-
-        /// <summary>
-        /// Constructs a new set of GUI elements for inspecting the limit object.
-        /// </summary>
-        /// <param name="prefix">Prefix that identifies the exact type of the limit type.</param>
-        /// <param name="limitData">Initial values to assign to the GUI elements.</param>
-        /// <param name="layout">Layout to append the GUI elements to.</param>
-        /// <param name="properties">A set of properties that are persisted by the parent inspector. Used for saving state.
-        ///                          </param>
-        public LimitCommonGUI(string prefix, LimitCommon limitData, GUILayout layout, SerializableProperties properties)
-        {
-            this.limitData = limitData;
-            this.properties = properties;
-            this.prefix = prefix;
-
-            hardFoldout.AcceptsKeyFocus = false;
-            hardFoldout.OnToggled += x =>
-            {
-                properties.SetBool(prefix + "_hardLimit_Expanded", x);
-                ToggleLimitFields();
-            };
-
-            contactDistanceField.OnChanged += x => { this.limitData.contactDist = x; MarkAsModified(); };
-            contactDistanceField.OnFocusLost += ConfirmModify;
-            contactDistanceField.OnConfirmed += ConfirmModify;
-
-            softFoldout.AcceptsKeyFocus = false;
-            softFoldout.OnToggled += x =>
-            {
-                properties.SetBool(prefix + "_softLimit_Expanded", x);
-                ToggleLimitFields();
-            };
-
-            restitutionField.OnChanged += x => { this.limitData.restitution = x; MarkAsModified(); };
-            restitutionField.OnFocusLost += ConfirmModify;
-
-            springFoldout.AcceptsKeyFocus = false;
-            springFoldout.OnToggled += x =>
-            {
-                properties.SetBool(prefix + "_spring_Expanded", x);
-                ToggleLimitFields();
-            };
-
-            hardLimitLayout = layout.AddLayoutX();
-            {
-                hardLimitLayout.AddSpace(10);
-
-                GUILayoutY hardLimitContentsLayout = hardLimitLayout.AddLayoutY();
-                hardLimitContentsLayout.AddElement(contactDistanceField);
-            }
-
-            softLimitLayout = layout.AddLayoutX();
-            layout.AddElement(softFoldout);
-            {
-                softLimitLayout.AddSpace(10);
-
-                GUILayoutY softLimitContentsLayout = softLimitLayout.AddLayoutY();
-                softLimitContentsLayout.AddElement(restitutionField);
-                softLimitContentsLayout.AddElement(springFoldout);
-                springLayout = softLimitContentsLayout.AddLayoutX();
-                {
-                    springLayout.AddSpace(10);
-
-                    GUILayoutY springContentsLayout = springLayout.AddLayoutY();
-                    springGUI = new SpringGUI(limitData.spring, springContentsLayout);
-                    springGUI.OnChanged += x => { this.limitData.spring = x; MarkAsModified(); };
-                    springGUI.OnConfirmed += ConfirmModify;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Marks the contents of the inspector as modified.
-        /// </summary>
-        private void MarkAsModified()
-        {
-            if (OnChanged != null)
-                OnChanged(limitData);
-        }
-
-        /// <summary>
-        /// Confirms any queued modifications.
-        /// </summary>
-        private void ConfirmModify()
-        {
-            if (OnConfirmed != null)
-                OnConfirmed();
-        }
-
-        /// <summary>
-        /// Hides or shows limit property GUI elements depending on set properties.
-        /// </summary>
-        private void ToggleLimitFields()
-        {
-            hardLimitLayout.Active = properties.GetBool(prefix + "_hardLimit_Expanded");
-            softLimitLayout.Active = properties.GetBool(prefix + "_softLimit_Expanded");
-            springLayout.Active = properties.GetBool(prefix + "_spring_Expanded");
-        }
-    }
-
-    /// <summary>
-    /// Draws GUI elements for inspecting an <see cref="LimitLinear"/> object.
-    /// </summary>
-    internal class LimitLinearGUI
-    {
-        private LimitLinear limitData;
-
-        private GUIFloatField limitExtentField = new GUIFloatField(new LocEdString("Extent"));
-        private LimitCommonGUI limitCommonGUI;
-
-        public Action<LimitLinear, LimitCommon> OnChanged;
-        public Action OnConfirmed;
-
-        /// <summary>
-        /// Current limit properties.
-        /// </summary>
-        public LimitLinear Limit
-        {
-            set
-            {
-                limitData = value;
-
-                limitExtentField.Value = limitData.extent;
-                limitCommonGUI.LimitData = value.GetBase();
-            }
-        }
-
-        /// <summary>
-        /// Constructs a new set of GUI elements for inspecting the limit object.
-        /// </summary>
-        /// <param name="limit">Initial values to assign to the GUI elements.</param>
-        /// <param name="layout">Layout to append the GUI elements to.</param>
-        /// <param name="properties">A set of properties that are persisted by the parent inspector. Used for saving state.
-        ///                          </param>
-        public LimitLinearGUI(LimitLinear limit, GUILayout layout, SerializableProperties properties)
-        {
-            limitData = limit;
-
-            limitExtentField.OnChanged += x => { limitData.extent = x; MarkAsModified(); };
-            limitExtentField.OnFocusLost += ConfirmModify;
-
-            layout.AddElement(limitExtentField);
-            limitCommonGUI = new LimitCommonGUI("linear", limit.GetBase(), layout, properties);
-            limitCommonGUI.OnChanged += x => MarkAsModified();
-            limitCommonGUI.OnConfirmed += ConfirmModify;
-        }
-
-        /// <summary>
-        /// Marks the contents of the inspector as modified.
-        /// </summary>
-        private void MarkAsModified()
-        {
-            if (OnChanged != null)
-                OnChanged(limitData, limitCommonGUI.LimitData);
-        }
-
-        /// <summary>
-        /// Confirms any queued modifications.
-        /// </summary>
-        private void ConfirmModify()
-        {
-            if (OnConfirmed != null)
-                OnConfirmed();
-        }
-    }
-
-    /// <summary>
-    /// Draws GUI elements for inspecting an <see cref="LimitLinearRange"/> object.
-    /// </summary>
-    internal class LimitLinearRangeGUI
-    {
-        private LimitLinearRange limitData;
-
-        private GUIFloatField limitLowerField = new GUIFloatField(new LocEdString("Lower"));
-        private GUIFloatField limitUpperField = new GUIFloatField(new LocEdString("Upper"));
-        private LimitCommonGUI limitCommonGUI;
-
-        public Action<LimitLinearRange, LimitCommon> OnChanged;
-        public Action OnConfirmed;
-
-        /// <summary>
-        /// Current limit properties.
-        /// </summary>
-        public LimitLinearRange Limit
-        {
-            set
-            {
-                limitData = value;
-
-                limitLowerField.Value = limitData.lower;
-                limitUpperField.Value = limitData.upper;
-                limitCommonGUI.LimitData = value.GetBase();
-            }
-        }
-
-        /// <summary>
-        /// Constructs a new set of GUI elements for inspecting the limit object.
-        /// </summary>
-        /// <param name="limit">Initial values to assign to the GUI elements.</param>
-        /// <param name="layout">Layout to append the GUI elements to.</param>
-        /// <param name="properties">A set of properties that are persisted by the parent inspector. Used for saving state.
-        ///                          </param>
-        public LimitLinearRangeGUI(LimitLinearRange limit, GUILayout layout, SerializableProperties properties)
-        {
-            this.limitData = limit;
-
-            limitLowerField.OnChanged += x => { limitData.lower = x; MarkAsModified(); };
-            limitLowerField.OnFocusLost += ConfirmModify;
-
-            limitUpperField.OnChanged += x => { limitData.upper = x; MarkAsModified(); };
-            limitUpperField.OnFocusLost += ConfirmModify;
-
-            layout.AddElement(limitLowerField);
-            layout.AddElement(limitUpperField);
-            limitCommonGUI = new LimitCommonGUI("linearRange", limit.GetBase(), layout, properties);
-            limitCommonGUI.OnChanged += x => MarkAsModified();
-            limitCommonGUI.OnConfirmed += ConfirmModify;
-        }
-
-        /// <summary>
-        /// Marks the contents of the inspector as modified.
-        /// </summary>
-        private void MarkAsModified()
-        {
-            if (OnChanged != null)
-                OnChanged(limitData, limitCommonGUI.LimitData);
-        }
-
-        /// <summary>
-        /// Confirms any queued modifications.
-        /// </summary>
-        private void ConfirmModify()
-        {
-            if (OnConfirmed != null)
-                OnConfirmed();
-        }
-    }
-
-    /// <summary>
-    /// Draws GUI elements for inspecting an <see cref="LimitAngularRange"/> object.
-    /// </summary>
-    internal class LimitAngularRangeGUI
-    {
-        private LimitAngularRange limitData;
-
-        private GUISliderField limitLowerField = new GUISliderField(0, 359, new LocEdString("Lower"));
-        private GUISliderField limitUpperField = new GUISliderField(0, 359, new LocEdString("Upper"));
-        private LimitCommonGUI limitCommonGUI;
-
-        public Action<LimitAngularRange, LimitCommon> OnChanged;
-        public Action OnConfirmed;
-
-        /// <summary>
-        /// Current limit properties.
-        /// </summary>
-        public LimitAngularRange Limit
-        {
-            set
-            {
-                limitData = value;
-
-                limitLowerField.Value = limitData.lower.Degrees;
-                limitUpperField.Value = limitData.upper.Degrees;
-                limitCommonGUI.LimitData = value.GetBase();
-            }
-        }
-
-        /// <summary>
-        /// Constructs a new set of GUI elements for inspecting the limit object.
-        /// </summary>
-        /// <param name="limit">Initial values to assign to the GUI elements.</param>
-        /// <param name="layout">Layout to append the GUI elements to.</param>
-        /// <param name="properties">A set of properties that are persisted by the parent inspector. Used for saving state.
-        ///                          </param>
-        public LimitAngularRangeGUI(LimitAngularRange limit, GUILayout layout, SerializableProperties properties)
-        {
-            this.limitData = limit;
-
-            limitLowerField.OnChanged += x => { limitData.lower = new Degree(x); MarkAsModified(); };
-            limitLowerField.OnFocusLost += ConfirmModify;
-
-            limitUpperField.OnChanged += x => { limitData.upper = new Degree(x); MarkAsModified(); };
-            limitUpperField.OnFocusLost += ConfirmModify;
-
-            layout.AddElement(limitLowerField);
-            layout.AddElement(limitUpperField);
-            limitCommonGUI = new LimitCommonGUI("angularRange", limit.GetBase(), layout, properties);
-            limitCommonGUI.OnChanged += x => MarkAsModified();
-            limitCommonGUI.OnConfirmed += ConfirmModify;
-        }
-
-        /// <summary>
-        /// Marks the contents of the inspector as modified.
-        /// </summary>
-        private void MarkAsModified()
-        {
-            if (OnChanged != null)
-                OnChanged(limitData, limitCommonGUI.LimitData);
-        }
-
-        /// <summary>
-        /// Confirms any queued modifications.
-        /// </summary>
-        private void ConfirmModify()
-        {
-            if (OnConfirmed != null)
-                OnConfirmed();
-        }
-    }
-
-    /// <summary>
-    /// Draws GUI elements for inspecting an <see cref="LimitConeRange"/> object.
-    /// </summary>
-    internal class LimitConeRangeGUI
-    {
-        private LimitConeRange limitData;
-
-        private GUISliderField yLimitAngleField = new GUISliderField(0, 180, new LocEdString("Y limit"));
-        private GUISliderField zLimitAngleField = new GUISliderField(0, 180, new LocEdString("Z limit"));
-        private LimitCommonGUI limitCommonGUI;
-
-        public Action<LimitConeRange, LimitCommon> OnChanged;
-        public Action OnConfirmed;
-
-        /// <summary>
-        /// Current limit properties.
-        /// </summary>
-        public LimitConeRange Limit
-        {
-            set
-            {
-                limitData = value;
-
-                yLimitAngleField.Value = limitData.yLimitAngle.Degrees;
-                zLimitAngleField.Value = limitData.zLimitAngle.Degrees;
-                limitCommonGUI.LimitData = value.GetBase();
-            }
-        }
-
-        /// <summary>
-        /// Constructs a new set of GUI elements for inspecting the limit object.
-        /// </summary>
-        /// <param name="limit">Initial values to assign to the GUI elements.</param>
-        /// <param name="layout">Layout to append the GUI elements to.</param>
-        /// <param name="properties">A set of properties that are persisted by the parent inspector. Used for saving state.
-        ///                          </param>
-        public LimitConeRangeGUI(LimitConeRange limit, GUILayout layout, SerializableProperties properties)
-        {
-            this.limitData = limit;
-
-            yLimitAngleField.OnChanged += x => { limitData.yLimitAngle = new Degree(x); MarkAsModified(); };
-            yLimitAngleField.OnFocusLost += ConfirmModify;
-
-            zLimitAngleField.OnChanged += x => { limitData.zLimitAngle = new Degree(x); MarkAsModified(); };
-            zLimitAngleField.OnFocusLost += ConfirmModify;
-
-            layout.AddElement(yLimitAngleField);
-            layout.AddElement(zLimitAngleField);
-            limitCommonGUI = new LimitCommonGUI("coneRange", limit.GetBase(), layout, properties);
-            limitCommonGUI.OnChanged += x => MarkAsModified();
-            limitCommonGUI.OnConfirmed += ConfirmModify;
-        }
-
-        /// <summary>
-        /// Marks the contents of the inspector as modified.
-        /// </summary>
-        private void MarkAsModified()
-        {
-            if (OnChanged != null)
-                OnChanged(limitData, limitCommonGUI.LimitData);
-        }
-
-        /// <summary>
-        /// Confirms any queued modifications.
-        /// </summary>
-        private void ConfirmModify()
-        {
-            if (OnConfirmed != null)
-                OnConfirmed();
-        }
-    }
-
-    /** @} */
-}

+ 8 - 88
Source/EditorManaged/Inspectors/SliderJointInspector.cs

@@ -14,97 +14,17 @@ namespace bs.Editor
     [CustomInspector(typeof(SliderJoint))]
     internal class SliderJointInspector : JointInspector
     {
-        private GUIToggleField enableLimitField = new GUIToggleField(new LocEdString("Enable limit"));
-        private LimitLinearRangeGUI limitGUI;
-
-        private GUILayoutX limitLayout;
-
         /// <inheritdoc/>
         protected internal override void Initialize()
         {
-            SliderJoint joint = InspectedObject as SliderJoint;
-
-            if (joint != null)
-                BuildGUI(joint);
-        }
-
-        /// <inheritdoc/>
-        protected internal override InspectableState Refresh()
-        {
-            SliderJoint joint = InspectedObject as SliderJoint;
-            if (joint == null)
-                return InspectableState.NotModified;
-
-            Refresh(joint);
-
-            InspectableState oldState = modifyState;
-            if (modifyState.HasFlag(InspectableState.Modified))
-                modifyState = InspectableState.NotModified;
-
-            return oldState;
-        }
-
-        /// <summary>
-        /// Creates GUI elements for fields specific to the slider joint.
-        /// </summary>
-        protected void BuildGUI(SliderJoint joint)
-        {
-            enableLimitField.OnChanged += x =>
-            {
-                joint.SetFlag(SliderJointFlag.Limit, x);
-                MarkAsModified();
-                ConfirmModify();
-
-                ToggleLimitFields(x);
-            };
-            
-            Layout.AddElement(enableLimitField);
-            limitLayout = Layout.AddLayoutX();
-            {
-                limitLayout.AddSpace(10);
-
-                GUILayoutY limitContentsLayout = limitLayout.AddLayoutY();
-                limitGUI = new LimitLinearRangeGUI(joint.Limit, limitContentsLayout, Persistent);
-                limitGUI.OnChanged += (x, y) =>
-                {
-                    joint.Limit = x;
-                    joint.Limit.SetBase(y);
-
-                    MarkAsModified();
-                };
-                limitGUI.OnConfirmed += ConfirmModify;
-            }
-            
-            ToggleLimitFields(joint.HasFlag(SliderJointFlag.Limit));
-
-            base.BuildGUI(joint, true);
-        }
-
-        /// <summary>
-        /// Updates all GUI elements from current values in the joint.
-        /// </summary>
-        /// <param name="joint">Joint to update the GUI from.</param>
-        protected void Refresh(SliderJoint joint)
-        {
-            bool enableLimit = joint.HasFlag(SliderJointFlag.Limit);
-            if (enableLimitField.Value != enableLimit)
-            {
-                enableLimitField.Value = enableLimit;
-                ToggleLimitFields(enableLimit);
-            }
-
-            limitGUI.Limit = joint.Limit;
-            
-            base.Refresh(joint);
-        }
-
-        /// <summary>
-        /// Hides or shows limit property GUI elements.
-        /// </summary>
-        /// <param name="enable">True to show, false to hide.</param>
-        private void ToggleLimitFields(bool enable)
-        {
-            limitLayout.Active = enable;
+            SliderJoint joint = (SliderJoint)InspectedObject;
+            BuildGUI(joint, true);
+
+            drawer.AddDefault(joint, typeof(SliderJoint));
+            drawer.AddField("Enable limit",
+                () => joint.HasFlag(SliderJointFlag.Limit),
+                x => joint.SetFlag(SliderJointFlag.Limit, x));
+            drawer.AddConditional("Limit", () => joint.HasFlag(SliderJointFlag.Limit));
         }
     }
 

+ 8 - 88
Source/EditorManaged/Inspectors/SphericalJointInspector.cs

@@ -14,97 +14,17 @@ namespace bs.Editor
     [CustomInspector(typeof(SphericalJoint))]
     internal class SphericalJointInspector : JointInspector
     {
-        private GUIToggleField enableLimitField = new GUIToggleField(new LocEdString("Enable limit"));
-        private LimitConeRangeGUI limitGUI;
-
-        private GUILayoutX limitLayout;
-
         /// <inheritdoc/>
         protected internal override void Initialize()
         {
-            SphericalJoint joint = InspectedObject as SphericalJoint;
-
-            if (joint != null)
-                BuildGUI(joint);
-        }
-
-        /// <inheritdoc/>
-        protected internal override InspectableState Refresh()
-        {
-            SphericalJoint joint = InspectedObject as SphericalJoint;
-            if (joint == null)
-                return InspectableState.NotModified;
-
-            Refresh(joint);
-
-            InspectableState oldState = modifyState;
-            if (modifyState.HasFlag(InspectableState.Modified))
-                modifyState = InspectableState.NotModified;
-
-            return oldState;
-        }
-
-        /// <summary>
-        /// Creates GUI elements for fields specific to the spherical joint.
-        /// </summary>
-        protected void BuildGUI(SphericalJoint joint)
-        {
-            enableLimitField.OnChanged += x =>
-            {
-                joint.SetFlag(SphericalJointFlag.Limit, x);
-                MarkAsModified();
-                ConfirmModify();
-
-                ToggleLimitFields(x);
-            };
-
-            Layout.AddElement(enableLimitField);
-            limitLayout = Layout.AddLayoutX();
-            {
-                limitLayout.AddSpace(10);
-
-                GUILayoutY limitContentsLayout = limitLayout.AddLayoutY();
-                limitGUI = new LimitConeRangeGUI(joint.Limit, limitContentsLayout, Persistent);
-                limitGUI.OnChanged += (x, y) =>
-                {
-                    joint.Limit = x;
-                    joint.Limit.SetBase(y);
-
-                    MarkAsModified();
-                };
-                limitGUI.OnConfirmed += ConfirmModify;
-            }
-
-            ToggleLimitFields(joint.HasFlag(SphericalJointFlag.Limit));
-
-            base.BuildGUI(joint, true);
-        }
-
-        /// <summary>
-        /// Updates all GUI elements from current values in the joint.
-        /// </summary>
-        /// <param name="joint">Joint to update the GUI from.</param>
-        protected void Refresh(SphericalJoint joint)
-        {
-            bool enableLimit = joint.HasFlag(SphericalJointFlag.Limit);
-            if (enableLimitField.Value != enableLimit)
-            {
-                enableLimitField.Value = enableLimit;
-                ToggleLimitFields(enableLimit);
-            }
-
-            limitGUI.Limit = joint.Limit;
-
-            base.Refresh(joint);
-        }
-
-        /// <summary>
-        /// Hides or shows limit property GUI elements.
-        /// </summary>
-        /// <param name="enable">True to show, false to hide.</param>
-        private void ToggleLimitFields(bool enable)
-        {
-            limitLayout.Active = enable;
+            SphericalJoint joint = (SphericalJoint) InspectedObject;
+            BuildGUI(joint, true);
+
+            drawer.AddDefault(joint, typeof(SphericalJoint));
+            drawer.AddField("Enable limit", 
+                () => joint.HasFlag(SphericalJointFlag.Limit), 
+                x => joint.SetFlag(SphericalJointFlag.Limit, x));
+            drawer.AddConditional("Limit", () => joint.HasFlag(SphericalJointFlag.Limit));
         }
     }
 

+ 2 - 4
Source/EditorManaged/Inspectors/SpriteTextureInspector.cs

@@ -17,8 +17,6 @@ namespace bs.Editor
     [CustomInspector(typeof(SpriteTexture))]
     internal class SpriteTextureInspector : Inspector
     {
-        private GenericInspectorDrawer genericDrawer;
-
         private GUILayoutWithBackground previewTitleLayout;
         private GUILayoutWithBackground previewContentLayout;
 
@@ -33,7 +31,7 @@ namespace bs.Editor
             if (spriteTexture == null)
                 return;
 
-            genericDrawer = new GenericInspectorDrawer(spriteTexture, new InspectableContext(Persistent), Layout);
+            drawer.AddDefault(spriteTexture);
 
             GUILayout previewLayout = PreviewGUI.AddLayoutY();
             previewTitleLayout = GUILayoutWithBackground.Create<GUILayoutX>(previewLayout, Builtin.WhiteTexture,
@@ -60,7 +58,7 @@ namespace bs.Editor
             if (spriteTexture == null)
                 return InspectableState.NotModified;
 
-            InspectableState state = genericDrawer.Refresh();
+            InspectableState state = drawer.Refresh();
             if (state != InspectableState.NotModified)
             {
                 EditorApplication.SetDirty(spriteTexture);

+ 3 - 73
Source/EditorManaged/Windows/Inspector/GenericInspector.cs

@@ -1,5 +1,7 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+
+using System;
 using System.Collections.Generic;
 using bs;
 
@@ -16,13 +18,6 @@ namespace bs.Editor
     internal sealed class GenericInspector : Inspector
     {
         private bool isEmpty = true;
-        private GenericInspectorDrawer drawer;
-
-        /// <inheritdoc/>
-        internal override void FocusOnField(string path)
-        {
-            drawer.FocusOnField(path);
-        }
 
         /// <inheritdoc/>
         protected internal override void Initialize()
@@ -30,10 +25,7 @@ namespace bs.Editor
             if (InspectedObject == null)
                 LoadResource();
 
-            Component inspectedComponent = InspectedObject as Component;
-
-            drawer = new GenericInspectorDrawer(InspectedObject, new InspectableContext(Persistent, inspectedComponent), 
-                Layout);
+            drawer.AddDefault(InspectedObject);
 
             isEmpty = drawer.Fields.Count == 0;
             base.SetVisible(!isEmpty);
@@ -52,67 +44,5 @@ namespace bs.Editor
         }
     }
 
-    /// <summary>
-    /// Helper class that draws the default inspector elements for an object, with an optional callback to render custom
-    /// inspectors for certain types.
-    /// </summary>
-    internal sealed class GenericInspectorDrawer
-    {
-        /// <summary>
-        /// List of fields created and updated by the drawer.
-        /// </summary>
-        public List<InspectableField> Fields { get; } = new List<InspectableField>();
-
-        /// <summary>
-        /// Creates new generic inspector field drawer for the specified object.
-        /// </summary>
-        /// <param name="obj">Object whose fields to create the GUI for.</param>
-        /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
-        /// <param name="layout">Parent layout that all the field GUI elements will be added to.</param>
-        /// <param name="overrideCallback">
-        /// Optional callback that allows you to override the look of individual fields in the object. If non-null the
-        /// callback will be called with information about every field in the provided object. If the callback returns
-        /// non-null that inspectable field will be used for drawing the GUI, otherwise the default inspector field type
-        /// will be used.
-        /// </param>
-        public GenericInspectorDrawer(object obj, InspectableContext context, GUILayoutY layout, 
-            InspectableField.FieldOverrideCallback overrideCallback = null)
-        {
-            if (obj == null)
-                return;
-
-            SerializableObject serializableObject = new SerializableObject(obj.GetType(), obj);
-            Fields = InspectableField.CreateFields(serializableObject, context, "", 0, layout, overrideCallback);
-        }
-
-        /// <summary>
-        /// Checks if contents of the inspector fields have been modified, and updates them if needed.
-        /// </summary>
-        /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
-        public InspectableState Refresh()
-        {
-            InspectableState state = InspectableState.NotModified;
-
-            int currentIndex = 0;
-            foreach (var field in Fields)
-            {
-                state |= field.Refresh(currentIndex);
-                currentIndex += field.GetNumLayoutElements();
-            }
-
-            return state;
-        }
-
-        /// <summary>
-        /// Changes keyboard focus to the provided field.
-        /// </summary>
-        /// <param name="path">Path to the field on the object being inspected.</param>
-        public void FocusOnField(string path)
-        {
-            InspectableField field = InspectableField.FindPath(path, 0, Fields);
-            field?.SetHasFocus();
-        }
-    }
-
     /** @} */
 }

+ 30 - 138
Source/EditorManaged/Windows/Inspector/InspectableField.cs

@@ -24,7 +24,9 @@ namespace bs.Editor
         protected SerializableProperty property;
         protected string title;
         protected string path;
+        protected string name;
         protected int depth;
+        protected bool active = true;
         protected SerializableProperty.FieldType type;
 
         /// <summary>
@@ -50,6 +52,20 @@ namespace bs.Editor
         /// </summary>
         public string Path => path;
 
+        /// <summary>
+        /// Name portion of the field path.
+        /// </summary>
+        public string Name => name;
+
+        /// <summary>
+        /// Activates or deactivates the underlying GUI elements.
+        /// </summary>
+        public bool Active
+        {
+            get => active;
+            set => SetActive(value);
+        }
+
         /// <summary>
         /// Creates a new inspectable field GUI for the specified property.
         /// </summary>
@@ -71,6 +87,15 @@ namespace bs.Editor
             this.type = type;
             this.depth = depth;
 
+            if (path != null)
+            {
+                int lastSlash = path.LastIndexOf('/');
+                if (lastSlash == -1)
+                    name = path;
+                else
+                    name = path.Substring(lastSlash);
+            }
+
             Property = property;
         }
 
@@ -249,148 +274,15 @@ namespace bs.Editor
         }
 
         /// <summary>
-        /// Allows the user to override the default inspector GUI for a specific field in an object. If this method
-        /// returns null the default field will be used instead.
-        /// </summary>
-        /// <param name="field">Field to generate inspector GUI for.</param>
-        /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
-        /// <param name="path">Full path to the provided field (includes name of this field and all parent fields).</param>
-        /// <param name="layout">Parent layout that all the field elements will be added to.</param>
-        /// <param name="layoutIndex">Index into the parent layout at which to insert the GUI elements for the field .</param>
-        /// <param name="depth">
-        /// Determines how deep within the inspector nesting hierarchy is this field. Some fields may contain other fields,
-        /// in which case you should increase this value by one.
-        /// </param>
-        /// <returns>
-        /// Inspectable field implementation that can be used for displaying the GUI for the provided field. Or null if
-        /// default field GUI should be used instead.
-        /// </returns>
-        public delegate InspectableField FieldOverrideCallback(SerializableField field, InspectableContext context, string path,
-            InspectableFieldLayout layout, int layoutIndex, int depth);
-
-        /// <summary>
-        /// Creates inspectable fields all the fields/properties of the specified object.
+        /// Activates or deactivates the underlying GUI elements.
         /// </summary>
-        /// <param name="obj">Object whose fields the GUI will be drawn for.</param>
-        /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
-        /// <param name="path">Full path to the field this provided object was retrieved from.</param>
-        /// <param name="depth">
-        /// Determines how deep within the inspector nesting hierarchy is this objects. Some fields may contain other
-        /// fields, in which case you should increase this value by one.
-        /// </param>
-        /// <param name="layout">Parent layout that all the field GUI elements will be added to.</param>
-        /// <param name="overrideCallback">
-        /// Optional callback that allows you to override the look of individual fields in the object. If non-null the
-        /// callback will be called with information about every field in the provided object. If the callback returns
-        /// non-null that inspectable field will be used for drawing the GUI, otherwise the default inspector field type
-        /// will be used.
-        /// </param>
-        public static List<InspectableField> CreateFields(SerializableObject obj, InspectableContext context, string path, 
-            int depth, GUILayoutY layout, FieldOverrideCallback overrideCallback = null)
+        protected virtual void SetActive(bool active)
         {
-            // Retrieve fields and sort by order
-            List<SerializableField> fields = new List<SerializableField>();
-
-            while (obj != null)
-            {
-                SerializableField[] subTypeFields = obj.Fields;
-                Array.Sort(subTypeFields,
-                    (x, y) =>
-                    {
-                        int orderX = x.Flags.HasFlag(SerializableFieldAttributes.Order) ? x.Style.Order : 0;
-                        int orderY = y.Flags.HasFlag(SerializableFieldAttributes.Order) ? y.Style.Order : 0;
-
-                        return orderX.CompareTo(orderY);
-                    });
-
-                fields.AddRange(subTypeFields);
-                obj = obj.Base;
-            }
-
-            // Generate per-field GUI while grouping by category
-            int rootIndex = 0;
-            int categoryIndex = 0;
-            string categoryName = null;
-            InspectableCategory category = null;
-
-            List<InspectableField> inspectableFields = new List<InspectableField>();
-            foreach (var field in fields)
+            if (this.active != active)
             {
-                if (!field.Flags.HasFlag(SerializableFieldAttributes.Inspectable))
-                    continue;
-
-                if (field.Flags.HasFlag(SerializableFieldAttributes.Category))
-                {
-                    string newCategory = field.Style.CategoryName;
-                    if (!string.IsNullOrEmpty(newCategory) && categoryName != newCategory)
-                    {
-                        string categoryPath = string.IsNullOrEmpty(path) ? $"[{newCategory}]" : $"{path}/[{newCategory}]";
-                        category = new InspectableCategory(context, newCategory, categoryPath, depth, 
-                            new InspectableFieldLayout(layout));
-
-                        category.Initialize(rootIndex);
-                        category.Refresh(rootIndex);
-                        rootIndex += category.GetNumLayoutElements();
-
-                        inspectableFields.Add(category);
-
-                        categoryName = newCategory;
-                        categoryIndex = 0;
-                    }
-                    else
-                    {
-                        categoryName = null;
-                        category = null;
-                    }
-                }
-
-                int currentIndex;
-                int childDepth;
-                GUILayoutY parentLayout;
-                if (category != null)
-                {
-                    currentIndex = categoryIndex;
-                    parentLayout = category.ChildLayout;
-                    childDepth = depth + 1;
-                }
-                else
-                {
-                    currentIndex = rootIndex;
-                    parentLayout = layout;
-                    childDepth = depth;
-                }
-
-                string fieldName = field.Name;
-                string readableName = GetReadableIdentifierName(fieldName);
-                string childPath = string.IsNullOrEmpty(path) ? fieldName : $"{path}/{fieldName}";
-
-                InspectableField inspectableField = null;
-
-                if(overrideCallback != null)
-                    inspectableField = overrideCallback(field, context, path, new InspectableFieldLayout(parentLayout), 
-                        currentIndex, depth);
-
-                if (inspectableField == null)
-                {
-                    inspectableField = CreateField(context, readableName, childPath,
-                        currentIndex, childDepth, new InspectableFieldLayout(parentLayout), field.GetProperty(), 
-                        InspectableFieldStyle.Create(field));
-                }
-
-                if (category != null)
-                    category.AddChild(inspectableField);
-                else
-                    inspectableFields.Add(inspectableField);
-
-                currentIndex += inspectableField.GetNumLayoutElements();
-
-                if (category != null)
-                    categoryIndex = currentIndex;
-                else
-                    rootIndex = currentIndex;
+                layout.SetActive(active);
+                this.active = active;
             }
-
-            return inspectableFields;
         }
 
         /// <summary>

+ 10 - 0
Source/EditorManaged/Windows/Inspector/InspectableFieldLayout.cs

@@ -84,6 +84,16 @@ namespace bs.Editor
 
             elements.Clear();
         }
+
+        /// <summary>
+        /// Activates or deactivates the underlying GUI elements.
+        /// </summary>
+        public void SetActive(bool active)
+        {
+            foreach (var element in elements)
+                element.Active = active;
+        }
+
     }
 
     /** @} */

+ 30 - 16
Source/EditorManaged/Windows/Inspector/InspectableObject.cs

@@ -20,7 +20,7 @@ namespace bs.Editor
         private const int IndentAmount = 5;
 
         private object propertyValue;
-        private List<InspectableField> children = new List<InspectableField>();
+        private InspectorFieldDrawer drawer;
         private InspectableFieldStyleInfo style;
 
         private GUILayoutY guiLayout;
@@ -124,21 +124,31 @@ namespace bs.Editor
                 BuildGUI(layoutIndex);
             }
 
-            InspectableState state = InspectableState.NotModified;
-            int currentIndex = 0;
-            for (int i = 0; i < children.Count; i++)
-            {
-                state |= children[i].Refresh(currentIndex);
-                currentIndex += children[i].GetNumLayoutElements();
-            }
+            if(drawer != null)
+                return drawer.Refresh();
 
-            return state;
+            return InspectableState.NotModified;
         }
 
         /// <inheritdoc />
         public override InspectableField FindPath(string path)
         {
-            return FindPath(path, depth, children);
+            if(drawer != null)
+                return FindPath(path, depth, drawer.Fields);
+
+            return null;
+        }
+
+        /// <inheritdoc />
+        protected override void SetActive(bool active)
+        {
+            if (drawer != null)
+            {
+                foreach (var entry in drawer.Fields)
+                    entry.Active = active;
+            }
+
+            base.SetActive(active);
         }
 
         /// <summary>
@@ -179,7 +189,7 @@ namespace bs.Editor
                     guiFoldout.OnToggled += OnFoldoutToggled;
                     guiInternalTitleLayout.AddElement(guiFoldout);
 
-                    if (!style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NotNull))
+                    if (!property.IsValueType && (style == null || !style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NotNull)))
                     {
                         GUIContent clearIcon = new GUIContent(
                             EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clear),
@@ -224,7 +234,14 @@ namespace bs.Editor
                         else
                             guiContentLayout = guiLayout;
 
-                        children = CreateFields(serializableObject, context, path, depth + 1, guiContentLayout);
+                        drawer = new InspectorFieldDrawer(context, guiContentLayout, path, depth + 1);
+                        drawer.AddDefault(serializableObject);
+
+                        if (!active)
+                        {
+                            foreach (var field in drawer.Fields)
+                                field.Active = false;
+                        }
                     }
                 }
                 else
@@ -259,10 +276,7 @@ namespace bs.Editor
             }
             else if (state == State.Filled)
             {
-                foreach (var child in children)
-                    child.Destroy();
-
-                children.Clear();
+                drawer?.Clear();
 
                 guiInternalTitleLayout?.Destroy();
                 guiInternalTitleLayout = null;

+ 322 - 2
Source/EditorManaged/Windows/Inspector/Inspector.cs

@@ -1,5 +1,7 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+
+using System;
 using System.Collections.Generic;
 using bs;
 
@@ -67,6 +69,8 @@ namespace bs.Editor
             get { return persistent; }
         }
 
+        protected InspectorFieldDrawer drawer;
+
         private GUIPanel rootGUI;
         private GUIPanel mainPanel;
         private GUIPanel previewPanel;
@@ -115,6 +119,7 @@ namespace bs.Editor
         internal virtual void Initialize(GUIPanel gui, object instance, SerializableProperties persistent)
         {
             InitializeBase(gui, null, persistent);
+            drawer = new InspectorFieldDrawer(new InspectableContext(Persistent, instance as Component), Layout);
 
             inspectedObject = instance;
 
@@ -135,6 +140,7 @@ namespace bs.Editor
             SerializableProperties persistent)
         {
             InitializeBase(mainGui, previewGui, persistent);
+            drawer = new InspectorFieldDrawer(new InspectableContext(Persistent), Layout);
 
             inspectedResourcePath = path;
 
@@ -146,7 +152,10 @@ namespace bs.Editor
         /// Changes keyboard focus to the provided field.
         /// </summary>
         /// <param name="path">Path to the field on the object being inspected.</param>
-        internal virtual void FocusOnField(string path) { }
+        internal virtual void FocusOnField(string path)
+        {
+            drawer.FocusOnField(path);
+        }
 
         /// <summary>
         /// Loads the currently inspected resource into the <see cref="InspectedObject"/> field. By default resources
@@ -185,7 +194,318 @@ namespace bs.Editor
         /// Checks if contents of the inspector have been modified, and updates them if needed.
         /// </summary>
         /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
-        protected internal abstract InspectableState Refresh();
+        protected internal virtual InspectableState Refresh()
+        {
+            return drawer.Refresh();
+        }
+    }
+
+    /// <summary>
+    /// Helper class that draws the inspector field elements for an object, allowing you to draw default set of fields,
+    /// override certain fields with your own, or add new fields manually.
+    /// </summary>
+    public sealed class InspectorFieldDrawer
+    {
+        /// <summary>
+        /// Contains information about a conditional that determines whether a field will be active or not.
+        /// </summary>
+        private struct ConditionalInfo
+        {
+            public Func<bool> callback;
+            public string field;
+
+            public ConditionalInfo(string field, Func<bool> callback)
+            {
+                this.field = field;
+                this.callback = callback;
+            }
+        }
+
+        /// <summary>
+        /// Allows the user to override the default inspector GUI for a specific field in an object. If this method
+        /// returns null the default field will be used instead.
+        /// </summary>
+        /// <param name="field">Field to generate inspector GUI for.</param>
+        /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
+        /// <param name="path">Full path to the provided field (includes name of this field and all parent fields).</param>
+        /// <param name="layout">Parent layout that all the field elements will be added to.</param>
+        /// <param name="layoutIndex">Index into the parent layout at which to insert the GUI elements for the field .</param>
+        /// <param name="depth">
+        /// Determines how deep within the inspector nesting hierarchy is this field. Some fields may contain other fields,
+        /// in which case you should increase this value by one.
+        /// </param>
+        /// <returns>
+        /// Inspectable field implementation that can be used for displaying the GUI for the provided field. Or null if
+        /// default field GUI should be used instead.
+        /// </returns>
+        public delegate InspectableField FieldOverrideCallback(SerializableField field, InspectableContext context, string path,
+            InspectableFieldLayout layout, int layoutIndex, int depth);
+
+        /// <summary>
+        /// List of fields created and updated by the drawer.
+        /// </summary>
+        public List<InspectableField> Fields { get; } = new List<InspectableField>();
+
+        private InspectableContext context;
+        private GUILayoutY layout;
+        private string path;
+        private int depth;
+        private int rootIndex;
+        private List<ConditionalInfo> conditionals;
+
+        /// <summary>
+        /// Creates new empty inspector field drawer.
+        /// </summary>
+        /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
+        /// <param name="layout">Parent layout that all the field GUI elements will be added to.</param>
+        /// <param name="path">Root path to be used for all created child fields.</param>
+        /// <param name="depth">
+        /// Determines how deep within the inspector nesting hierarchy will the generated fields be. Some fields may
+        /// contain other fields, in which case you should increase this value by one.
+        /// </param>
+        public InspectorFieldDrawer(InspectableContext context, GUILayoutY layout, string path = "", int depth = 0)
+        {
+            this.context = context;
+            this.layout = layout;
+            this.path = path;
+            this.depth = depth;
+        }
+
+        /// <summary>
+        /// Creates the default inspector GUI for the provided object.
+        /// </summary>
+        /// <param name="obj">Object whose fields to create the GUI for.</param>
+        /// <param name="subType">
+        /// If not null, the added fields will be limited to this particular type (not including any base types the actual
+        /// object type might be a part of). If null, then fields for the entire class hierarchy of the provided object's
+        /// type will be created.
+        /// </param>
+        /// <param name="overrideCallback">
+        /// Optional callback that allows you to override the look of individual fields in the object. If non-null the
+        /// callback will be called with information about every field in the provided object. If the callback returns
+        /// non-null that inspectable field will be used for drawing the GUI, otherwise the default inspector field type
+        /// will be used.
+        /// </param>
+        public void AddDefault(object obj, Type subType = null, FieldOverrideCallback overrideCallback = null)
+        {
+            if (obj == null)
+                return;
+
+            SerializableObject serializableObject = new SerializableObject(obj.GetType(), obj);
+            AddDefault(serializableObject, subType, overrideCallback);
+        }
+
+        /// <summary>
+        /// Creates the default inspector GUI for the provided object.
+        /// </summary>
+        /// <param name="obj">Object whose fields to create the GUI for.</param>
+        /// <param name="subType">
+        /// If not null, the added fields will be limited to this particular type (not including any base types the actual
+        /// object type might be a part of). If null, then fields for the entire class hierarchy of the provided object's
+        /// type will be created.
+        /// </param>
+        /// <param name="overrideCallback">
+        /// Optional callback that allows you to override the look of individual fields in the object. If non-null the
+        /// callback will be called with information about every field in the provided object. If the callback returns
+        /// non-null that inspectable field will be used for drawing the GUI, otherwise the default inspector field type
+        /// will be used.
+        /// </param>
+        public void AddDefault(SerializableObject obj, Type subType = null, FieldOverrideCallback overrideCallback = null)
+        {
+            if (obj == null)
+                return;
+
+            // Retrieve fields and sort by order
+            List<SerializableField> fields = new List<SerializableField>();
+            while (obj != null)
+            {
+                if (subType == null || subType == obj.Type)
+                {
+                    SerializableField[] subTypeFields = obj.Fields;
+                    Array.Sort(subTypeFields,
+                        (x, y) =>
+                        {
+                            int orderX = x.Flags.HasFlag(SerializableFieldAttributes.Order) ? x.Style.Order : 0;
+                            int orderY = y.Flags.HasFlag(SerializableFieldAttributes.Order) ? y.Style.Order : 0;
+
+                            return orderX.CompareTo(orderY);
+                        });
+
+                    fields.AddRange(subTypeFields);
+                }
+
+                obj = obj.Base;
+            }
+
+            // Generate per-field GUI while grouping by category
+            int categoryIndex = 0;
+            string categoryName = null;
+            InspectableCategory category = null;
+
+            foreach (var field in fields)
+            {
+                if (!field.Flags.HasFlag(SerializableFieldAttributes.Inspectable))
+                    continue;
+
+                if (field.Flags.HasFlag(SerializableFieldAttributes.Category))
+                {
+                    string newCategory = field.Style.CategoryName;
+                    if (!string.IsNullOrEmpty(newCategory) && categoryName != newCategory)
+                    {
+                        string categoryPath = string.IsNullOrEmpty(path) ? $"[{newCategory}]" : $"{path}/[{newCategory}]";
+                        category = new InspectableCategory(context, newCategory, categoryPath, depth,
+                            new InspectableFieldLayout(layout));
+
+                        category.Initialize(rootIndex);
+                        category.Refresh(rootIndex);
+                        rootIndex += category.GetNumLayoutElements();
+
+                        Fields.Add(category);
+
+                        categoryName = newCategory;
+                        categoryIndex = 0;
+                    }
+                    else
+                    {
+                        categoryName = null;
+                        category = null;
+                    }
+                }
+
+                int currentIndex;
+                int childDepth;
+                GUILayoutY parentLayout;
+                if (category != null)
+                {
+                    currentIndex = categoryIndex;
+                    parentLayout = category.ChildLayout;
+                    childDepth = depth + 1;
+                }
+                else
+                {
+                    currentIndex = rootIndex;
+                    parentLayout = layout;
+                    childDepth = depth;
+                }
+
+                string fieldName = field.Name;
+                string readableName = InspectableField.GetReadableIdentifierName(fieldName);
+                string childPath = string.IsNullOrEmpty(path) ? fieldName : $"{path}/{fieldName}";
+
+                InspectableField inspectableField = null;
+
+                if (overrideCallback != null)
+                    inspectableField = overrideCallback(field, context, path, new InspectableFieldLayout(parentLayout),
+                        currentIndex, depth);
+
+                if (inspectableField == null)
+                {
+                    inspectableField = InspectableField.CreateField(context, readableName, childPath,
+                        currentIndex, childDepth, new InspectableFieldLayout(parentLayout), field.GetProperty(),
+                        InspectableFieldStyle.Create(field));
+                }
+
+                if (category != null)
+                    category.AddChild(inspectableField);
+                else
+                    Fields.Add(inspectableField);
+
+                currentIndex += inspectableField.GetNumLayoutElements();
+
+                if (category != null)
+                    categoryIndex = currentIndex;
+                else
+                    rootIndex = currentIndex;
+            }
+        }
+
+        /// <summary>
+        /// Adds a custom inspectable field with a custom getter and setter.
+        /// </summary>
+        /// <typeparam name="T">Type the field is inspecting.</typeparam>
+        /// <param name="name">Name of the field.</param>
+        /// <param name="getter">Method that returns the current value of the field.</param>
+        /// <param name="setter">Method that sets a new value of the field.</param>
+        public void AddField<T>(string name, Func<T> getter, Action<T> setter)
+        {
+            string childPath = string.IsNullOrEmpty(path) ? name : $"{path}/{name}";
+
+            SerializableProperty property = SerializableProperty.Create(getter, setter);
+            InspectableField inspectableField = InspectableField.CreateField(context, name, childPath,
+                rootIndex, depth, new InspectableFieldLayout(layout), property);
+
+            Fields.Add(inspectableField);
+
+            rootIndex += inspectableField.GetNumLayoutElements();
+        }
+
+        /// <summary>
+        /// Adds a condition that determines whether a field will be shown or hidde.
+        /// </summary>
+        /// <param name="field">Name of the field the condition applies to.</param>
+        /// <param name="callback">The callback that returns true if the field should be shown, false otherwise.</param>
+        public void AddConditional(string field, Func<bool> callback)
+        {
+            if(conditionals == null)
+                conditionals = new List<ConditionalInfo>();
+
+            conditionals.Add(new ConditionalInfo(field, callback));
+        }
+
+        /// <summary>
+        /// Checks if contents of the inspector fields have been modified, and updates them if needed.
+        /// </summary>
+        /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
+        public InspectableState Refresh()
+        {
+            InspectableState state = InspectableState.NotModified;
+
+            int currentIndex = 0;
+            foreach (var field in Fields)
+            {
+                state |= field.Refresh(currentIndex);
+                currentIndex += field.GetNumLayoutElements();
+
+                if (conditionals != null)
+                {
+                    foreach (var conditional in conditionals)
+                    {
+                        if (conditional.field == field.Name && conditional.callback != null)
+                        {
+                            bool active = conditional.callback();
+
+                            if (active != field.Active)
+                                field.Active = active;
+                        }
+                    }
+                }
+            }
+
+            return state;
+        }
+
+        /// <summary>
+        /// Destroys and removes all the child inspector fields.
+        /// </summary>
+        public void Clear()
+        {
+            foreach(var field in Fields)
+                field.Destroy();
+
+            Fields.Clear();
+            conditionals?.Clear();
+            rootIndex = 0;
+        }
+
+        /// <summary>
+        /// Changes keyboard focus to the provided field.
+        /// </summary>
+        /// <param name="path">Path to the field on the object being inspected.</param>
+        public void FocusOnField(string path)
+        {
+            InspectableField field = InspectableField.FindPath(path, 0, Fields);
+            field?.SetHasFocus();
+        }
     }
 
     /** @} */

+ 10 - 6
Source/EditorManaged/Windows/Scene/SceneCameraSettingsWindow.cs

@@ -17,9 +17,9 @@ namespace bs.Editor
     {
         private const string ExpandStatesKey = "SceneCamera0_ExpandStates";
 
-        private GenericInspectorDrawer guiViewSettings;
-        private GenericInspectorDrawer guiMovementSettings;
-        private GenericInspectorDrawer guiRenderSettings;
+        private InspectorFieldDrawer guiViewSettings;
+        private InspectorFieldDrawer guiMovementSettings;
+        private InspectorFieldDrawer guiRenderSettings;
 
         private SceneCameraViewSettings viewSettings;
         private SceneCameraMoveSettings moveSettings;
@@ -86,9 +86,13 @@ namespace bs.Editor
             vertLayout.AddElement(new GUILabel(new LocEdString("Render Settings"), EditorStyles.LabelBold));
             GUILayoutY renderSettingsLayout = vertLayout.AddLayoutY();
 
-            guiViewSettings = new GenericInspectorDrawer(viewSettings, inspectableContext, viewSettingsLayout);
-            guiMovementSettings = new GenericInspectorDrawer(moveSettings, inspectableContext, moveSettingsLayout);
-            guiRenderSettings = new GenericInspectorDrawer(renderSettings, inspectableContext, renderSettingsLayout);
+            guiViewSettings = new InspectorFieldDrawer(inspectableContext, viewSettingsLayout);
+            guiMovementSettings = new InspectorFieldDrawer(inspectableContext, moveSettingsLayout);
+            guiRenderSettings = new InspectorFieldDrawer(inspectableContext, renderSettingsLayout);
+
+            guiViewSettings.AddDefault(viewSettings);
+            guiMovementSettings.AddDefault(moveSettings);
+            guiRenderSettings.AddDefault(renderSettings);
 
             mainLayout.AddSpace(5);
             GUILayout buttonCenterLayout = mainLayout.AddLayoutX();

+ 1 - 1
Source/bsf

@@ -1 +1 @@
-Subproject commit 3cf98b70cfa5f2dd1f89f25161c0c15a4fb75141
+Subproject commit cba66325eee6d8ab73452fed3e3a516bf4de24fb